@akcelik/strct 5.10.0 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1890,6 +1890,16 @@ class StrctListItem {
1890
1890
  * row at once; this is the per-row form.
1891
1891
  */
1892
1892
  wrap = input(false, { ...(ngDevMode ? { debugName: "wrap" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1893
+ /**
1894
+ * The row cannot be activated — a conversation that cannot be switched away
1895
+ * from while a reply arrives. It keeps its tab stop and says
1896
+ * `aria-disabled`, so the reason can be read; a keyboard or touch user
1897
+ * never sees a `title`.
1898
+ */
1899
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1900
+ /** Why it cannot be activated, read as the row's description. */
1901
+ disabledReason = input('', /* @ts-ignore */
1902
+ ...(ngDevMode ? [{ debugName: "disabledReason" }] : /* istanbul ignore next */ []));
1893
1903
  /** Marks the current row (`aria-current`). */
1894
1904
  selected = input(false, { ...(ngDevMode ? { debugName: "selected" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1895
1905
  /** Optional leading rail, as `strct-card [status]`. */
@@ -1898,16 +1908,19 @@ class StrctListItem {
1898
1908
  /** Emitted when an `interactive` row is activated. */
1899
1909
  activated = output();
1900
1910
  onActivate() {
1901
- if (this.interactive())
1911
+ if (this.interactive() && !this.disabled())
1902
1912
  this.activated.emit();
1903
1913
  }
1904
1914
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctListItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
1905
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctListItem, isStandalone: true, selector: "strct-list-item", inputs: { interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activated: "activated" }, host: { attributes: { "role": "listitem" }, properties: { "class.strct-li--interactive": "interactive()", "class.strct-li--wrap": "wrap()", "class.strct-li--selected": "selected()", "class.strct-li--status": "status()", "attr.data-status": "status()" }, classAttribute: "strct-li" }, ngImport: i0, template: `
1915
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctListItem, isStandalone: true, selector: "strct-list-item", inputs: { interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, disabledReason: { classPropertyName: "disabledReason", publicName: "disabledReason", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activated: "activated" }, host: { attributes: { "role": "listitem" }, properties: { "class.strct-li--interactive": "interactive()", "class.strct-li--wrap": "wrap()", "class.strct-li--disabled": "disabled()", "class.strct-li--selected": "selected()", "class.strct-li--status": "status()", "attr.data-status": "status()" }, classAttribute: "strct-li" }, ngImport: i0, template: `
1906
1916
  <div
1907
1917
  class="strct-li__main"
1908
1918
  [attr.role]="interactive() ? 'button' : null"
1909
1919
  [attr.tabindex]="interactive() ? 0 : null"
1910
1920
  [attr.aria-current]="selected() ? 'true' : null"
1921
+ [attr.aria-disabled]="disabled() ? 'true' : null"
1922
+ [attr.title]="disabledReason() || null"
1923
+ [attr.aria-description]="disabled() ? disabledReason() || null : null"
1911
1924
  (click)="onActivate()"
1912
1925
  (keydown.enter)="onActivate()"
1913
1926
  (keydown.space)="$event.preventDefault(); onActivate()"
@@ -1920,7 +1933,7 @@ class StrctListItem {
1920
1933
  <span class="strct-li__meta"><ng-content select="[strctListItemMeta]" /></span>
1921
1934
  </div>
1922
1935
  <span class="strct-li__trailing"><ng-content select="[strctListItemTrailing]" /></span>
1923
- `, isInline: true, styles: [".strct-li{display:flex;align-items:center;gap:var(--space-2);padding-inline:var(--space-3);position:relative}.strct-list--flush .strct-li{padding-inline:0}.strct-list--dividers .strct-li+.strct-li{border-block-start:1px solid var(--b1)}.strct-li__main{display:flex;align-items:center;gap:var(--space-2);flex:1;min-width:0;min-height:44px;padding-block:6px;text-align:start;border-radius:var(--radius-sm)}.strct-list--dense .strct-li__main{min-height:32px;padding-block:2px}.strct-li--interactive .strct-li__main{cursor:pointer}.strct-li--interactive:hover{background:var(--bg-2)}.strct-li--selected{background:var(--acc-s)}.strct-li__main:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-li--status:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;width:2px;background:var(--t3)}.strct-li[data-status=accent]:before{background:var(--acc)}.strct-li[data-status=success]:before{background:var(--success)}.strct-li[data-status=warning]:before{background:var(--warning)}.strct-li[data-status=critical]:before{background:var(--critical)}.strct-li--wrap .strct-li__main,.strct-list--wrap .strct-li__main{align-items:flex-start}.strct-li--wrap .strct-li__leading,.strct-list--wrap .strct-li__leading{min-height:calc(var(--text-md) * 1.45)}.strct-li--wrap .strct-li__title,.strct-li--wrap .strct-li__desc,.strct-list--wrap .strct-li__title,.strct-list--wrap .strct-li__desc{overflow:visible;text-overflow:clip;white-space:normal;overflow-wrap:anywhere}.strct-li--wrap .strct-li__meta,.strct-list--wrap .strct-li__meta{align-self:flex-start;min-height:calc(var(--text-md) * 1.45)}.strct-li__leading:empty,.strct-li__meta:empty,.strct-li__desc:empty,.strct-li__trailing:empty{display:none}.strct-li__leading{display:inline-flex;align-items:center;flex:none}.strct-li__text{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}.strct-li__title{font-size:var(--text-md);color:var(--t1);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-li__desc{font-size:var(--text-sm);color:var(--t3);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-li__meta{display:inline-flex;align-items:center;flex:none;font-size:var(--text-sm);color:var(--t3);white-space:nowrap}.strct-li__trailing{display:inline-flex;align-items:center;gap:var(--space-2);flex:none}@container (max-width: 360px){.strct-li__main{flex-wrap:wrap}.strct-li__meta{flex-basis:100%;padding-inline-start:0}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1936
+ `, isInline: true, styles: [".strct-li{display:flex;align-items:center;gap:var(--space-2);padding-inline:var(--space-3);position:relative}.strct-list--flush .strct-li{padding-inline:0}.strct-list--dividers .strct-li+.strct-li{border-block-start:1px solid var(--b1)}.strct-li__main{display:flex;align-items:center;gap:var(--space-2);flex:1;min-width:0;min-height:44px;padding-block:6px;text-align:start;border-radius:var(--radius-sm)}.strct-list--dense .strct-li__main{min-height:32px;padding-block:2px}.strct-li--interactive .strct-li__main{cursor:pointer}.strct-li--disabled .strct-li__main{cursor:not-allowed;opacity:.6}.strct-li--disabled.strct-li--interactive:hover{background:none}.strct-li--interactive:hover{background:var(--bg-2)}.strct-li--selected{background:var(--acc-s)}.strct-li__main:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-li--status:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;width:2px;background:var(--t3)}.strct-li[data-status=accent]:before{background:var(--acc)}.strct-li[data-status=success]:before{background:var(--success)}.strct-li[data-status=warning]:before{background:var(--warning)}.strct-li[data-status=critical]:before{background:var(--critical)}.strct-li--wrap .strct-li__main,.strct-list--wrap .strct-li__main{align-items:flex-start}.strct-li--wrap .strct-li__leading,.strct-list--wrap .strct-li__leading{min-height:calc(var(--text-md) * 1.45)}.strct-li--wrap .strct-li__title,.strct-li--wrap .strct-li__desc,.strct-list--wrap .strct-li__title,.strct-list--wrap .strct-li__desc{overflow:visible;text-overflow:clip;white-space:normal;overflow-wrap:anywhere}.strct-li--wrap .strct-li__meta,.strct-list--wrap .strct-li__meta{align-self:flex-start;min-height:calc(var(--text-md) * 1.45)}.strct-li__leading:empty,.strct-li__meta:empty,.strct-li__desc:empty,.strct-li__trailing:empty{display:none}.strct-li__leading{display:inline-flex;align-items:center;flex:none}.strct-li__text{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}.strct-li__title{font-size:var(--text-md);color:var(--t1);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-li__desc{font-size:var(--text-sm);color:var(--t3);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-li__meta{display:inline-flex;align-items:center;flex:none;font-size:var(--text-sm);color:var(--t3);white-space:nowrap}.strct-li__trailing{display:inline-flex;align-items:center;gap:var(--space-2);flex:none}@container (max-width: 360px){.strct-li__main{flex-wrap:wrap}.strct-li__meta{flex-basis:100%;padding-inline-start:0}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1924
1937
  }
1925
1938
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctListItem, decorators: [{
1926
1939
  type: Component,
@@ -1930,6 +1943,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1930
1943
  [attr.role]="interactive() ? 'button' : null"
1931
1944
  [attr.tabindex]="interactive() ? 0 : null"
1932
1945
  [attr.aria-current]="selected() ? 'true' : null"
1946
+ [attr.aria-disabled]="disabled() ? 'true' : null"
1947
+ [attr.title]="disabledReason() || null"
1948
+ [attr.aria-description]="disabled() ? disabledReason() || null : null"
1933
1949
  (click)="onActivate()"
1934
1950
  (keydown.enter)="onActivate()"
1935
1951
  (keydown.space)="$event.preventDefault(); onActivate()"
@@ -1947,11 +1963,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1947
1963
  role: 'listitem',
1948
1964
  '[class.strct-li--interactive]': 'interactive()',
1949
1965
  '[class.strct-li--wrap]': 'wrap()',
1966
+ '[class.strct-li--disabled]': 'disabled()',
1950
1967
  '[class.strct-li--selected]': 'selected()',
1951
1968
  '[class.strct-li--status]': 'status()',
1952
1969
  '[attr.data-status]': 'status()',
1953
- }, styles: [".strct-li{display:flex;align-items:center;gap:var(--space-2);padding-inline:var(--space-3);position:relative}.strct-list--flush .strct-li{padding-inline:0}.strct-list--dividers .strct-li+.strct-li{border-block-start:1px solid var(--b1)}.strct-li__main{display:flex;align-items:center;gap:var(--space-2);flex:1;min-width:0;min-height:44px;padding-block:6px;text-align:start;border-radius:var(--radius-sm)}.strct-list--dense .strct-li__main{min-height:32px;padding-block:2px}.strct-li--interactive .strct-li__main{cursor:pointer}.strct-li--interactive:hover{background:var(--bg-2)}.strct-li--selected{background:var(--acc-s)}.strct-li__main:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-li--status:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;width:2px;background:var(--t3)}.strct-li[data-status=accent]:before{background:var(--acc)}.strct-li[data-status=success]:before{background:var(--success)}.strct-li[data-status=warning]:before{background:var(--warning)}.strct-li[data-status=critical]:before{background:var(--critical)}.strct-li--wrap .strct-li__main,.strct-list--wrap .strct-li__main{align-items:flex-start}.strct-li--wrap .strct-li__leading,.strct-list--wrap .strct-li__leading{min-height:calc(var(--text-md) * 1.45)}.strct-li--wrap .strct-li__title,.strct-li--wrap .strct-li__desc,.strct-list--wrap .strct-li__title,.strct-list--wrap .strct-li__desc{overflow:visible;text-overflow:clip;white-space:normal;overflow-wrap:anywhere}.strct-li--wrap .strct-li__meta,.strct-list--wrap .strct-li__meta{align-self:flex-start;min-height:calc(var(--text-md) * 1.45)}.strct-li__leading:empty,.strct-li__meta:empty,.strct-li__desc:empty,.strct-li__trailing:empty{display:none}.strct-li__leading{display:inline-flex;align-items:center;flex:none}.strct-li__text{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}.strct-li__title{font-size:var(--text-md);color:var(--t1);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-li__desc{font-size:var(--text-sm);color:var(--t3);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-li__meta{display:inline-flex;align-items:center;flex:none;font-size:var(--text-sm);color:var(--t3);white-space:nowrap}.strct-li__trailing{display:inline-flex;align-items:center;gap:var(--space-2);flex:none}@container (max-width: 360px){.strct-li__main{flex-wrap:wrap}.strct-li__meta{flex-basis:100%;padding-inline-start:0}}\n"] }]
1954
- }], propDecorators: { interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], activated: [{ type: i0.Output, args: ["activated"] }] } });
1970
+ }, styles: [".strct-li{display:flex;align-items:center;gap:var(--space-2);padding-inline:var(--space-3);position:relative}.strct-list--flush .strct-li{padding-inline:0}.strct-list--dividers .strct-li+.strct-li{border-block-start:1px solid var(--b1)}.strct-li__main{display:flex;align-items:center;gap:var(--space-2);flex:1;min-width:0;min-height:44px;padding-block:6px;text-align:start;border-radius:var(--radius-sm)}.strct-list--dense .strct-li__main{min-height:32px;padding-block:2px}.strct-li--interactive .strct-li__main{cursor:pointer}.strct-li--disabled .strct-li__main{cursor:not-allowed;opacity:.6}.strct-li--disabled.strct-li--interactive:hover{background:none}.strct-li--interactive:hover{background:var(--bg-2)}.strct-li--selected{background:var(--acc-s)}.strct-li__main:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-li--status:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;width:2px;background:var(--t3)}.strct-li[data-status=accent]:before{background:var(--acc)}.strct-li[data-status=success]:before{background:var(--success)}.strct-li[data-status=warning]:before{background:var(--warning)}.strct-li[data-status=critical]:before{background:var(--critical)}.strct-li--wrap .strct-li__main,.strct-list--wrap .strct-li__main{align-items:flex-start}.strct-li--wrap .strct-li__leading,.strct-list--wrap .strct-li__leading{min-height:calc(var(--text-md) * 1.45)}.strct-li--wrap .strct-li__title,.strct-li--wrap .strct-li__desc,.strct-list--wrap .strct-li__title,.strct-list--wrap .strct-li__desc{overflow:visible;text-overflow:clip;white-space:normal;overflow-wrap:anywhere}.strct-li--wrap .strct-li__meta,.strct-list--wrap .strct-li__meta{align-self:flex-start;min-height:calc(var(--text-md) * 1.45)}.strct-li__leading:empty,.strct-li__meta:empty,.strct-li__desc:empty,.strct-li__trailing:empty{display:none}.strct-li__leading{display:inline-flex;align-items:center;flex:none}.strct-li__text{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}.strct-li__title{font-size:var(--text-md);color:var(--t1);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-li__desc{font-size:var(--text-sm);color:var(--t3);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-li__meta{display:inline-flex;align-items:center;flex:none;font-size:var(--text-sm);color:var(--t3);white-space:nowrap}.strct-li__trailing{display:inline-flex;align-items:center;gap:var(--space-2);flex:none}@container (max-width: 360px){.strct-li__main{flex-wrap:wrap}.strct-li__meta{flex-basis:100%;padding-inline-start:0}}\n"] }]
1971
+ }], propDecorators: { interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], disabledReason: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledReason", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], activated: [{ type: i0.Output, args: ["activated"] }] } });
1955
1972
  /**
1956
1973
  * A short list of things with a status — alarms, findings, clusters in a
1957
1974
  * summary. A table needs a header row and columns, a timeline implies time
@@ -2379,6 +2396,16 @@ class StrctTag {
2379
2396
  */
2380
2397
  activateLabel = input('', /* @ts-ignore */
2381
2398
  ...(ngDevMode ? [{ debugName: "activateLabel" }] : /* istanbul ignore next */ []));
2399
+ /**
2400
+ * The tag's body is a link to this address — an object chip in a reply opens
2401
+ * the object's page. A link is what a middle click, a Ctrl/Cmd-click and
2402
+ * "copy link address" need; `interactive` cannot give them.
2403
+ */
2404
+ href = input(null, /* @ts-ignore */
2405
+ ...(ngDevMode ? [{ debugName: "href" }] : /* istanbul ignore next */ []));
2406
+ /** `_blank` adds the `rel` a new tab needs. */
2407
+ target = input('', /* @ts-ignore */
2408
+ ...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
2382
2409
  /**
2383
2410
  * The body becomes a control: pointer, hover, focus ring, and `activated` on
2384
2411
  * click / Enter / Space. The × stays a separate tab stop, so "open it" and
@@ -2399,21 +2426,38 @@ class StrctTag {
2399
2426
  this.activated.emit();
2400
2427
  }
2401
2428
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctTag, deps: [], target: i0.ɵɵFactoryTarget.Component });
2402
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctTag, isStandalone: true, selector: "strct-tag", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null }, activateLabel: { classPropertyName: "activateLabel", publicName: "activateLabel", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, mono: { classPropertyName: "mono", publicName: "mono", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed", activated: "activated" }, host: { properties: { "class.strct-tag--interactive": "interactive() && !disabled()", "class.strct-tag--pill": "shape() === 'pill'", "class.strct-tag--mono": "mono()", "class.strct-tag--accent": "status() === 'accent'", "class.strct-tag--success": "status() === 'success'", "class.strct-tag--warning": "status() === 'warning'", "class.strct-tag--critical": "status() === 'critical'" }, classAttribute: "strct-tag" }, ngImport: i0, template: `
2403
- <span
2404
- class="strct-tag__text"
2405
- [attr.role]="interactive() ? 'button' : null"
2406
- [attr.tabindex]="interactive() && !disabled() ? 0 : null"
2407
- [attr.aria-label]="interactive() && activateLabel() ? activateLabel() : null"
2408
- (click)="onActivate()"
2409
- (keydown.enter)="onActivate()"
2410
- (keydown.space)="$event.preventDefault(); onActivate()"
2411
- >
2412
- <!-- Declared before the catch-all so the selector wins; it renders first
2429
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctTag, isStandalone: true, selector: "strct-tag", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null }, activateLabel: { classPropertyName: "activateLabel", publicName: "activateLabel", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, mono: { classPropertyName: "mono", publicName: "mono", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed", activated: "activated" }, host: { properties: { "class.strct-tag--interactive": "interactive() && !disabled()", "class.strct-tag--pill": "shape() === 'pill'", "class.strct-tag--mono": "mono()", "class.strct-tag--accent": "status() === 'accent'", "class.strct-tag--success": "status() === 'success'", "class.strct-tag--warning": "status() === 'warning'", "class.strct-tag--critical": "status() === 'critical'" }, classAttribute: "strct-tag" }, ngImport: i0, template: `
2430
+ <!-- With an href the body is a real link: Ctrl/Cmd-click opens a tab, the
2431
+ address shows on hover and it can be copied as a link — none of which
2432
+ a role="button" can do. The same text, the same focus ring. -->
2433
+ @if (href() && !disabled()) {
2434
+ <a
2435
+ class="strct-tag__text strct-tag__text--link"
2436
+ [attr.href]="href()"
2437
+ [attr.target]="target() || null"
2438
+ [attr.rel]="target() === '_blank' ? 'noopener noreferrer' : null"
2439
+ [attr.aria-label]="activateLabel() || null"
2440
+ (click)="onActivate()"
2441
+ >
2442
+ <ng-content select="[strctTagLeading]" />
2443
+ <ng-content />
2444
+ </a>
2445
+ } @else {
2446
+ <span
2447
+ class="strct-tag__text"
2448
+ [attr.role]="interactive() ? 'button' : null"
2449
+ [attr.tabindex]="interactive() && !disabled() ? 0 : null"
2450
+ [attr.aria-label]="interactive() && activateLabel() ? activateLabel() : null"
2451
+ (click)="onActivate()"
2452
+ (keydown.enter)="onActivate()"
2453
+ (keydown.space)="$event.preventDefault(); onActivate()"
2454
+ >
2455
+ <!-- Declared before the catch-all so the selector wins; it renders first
2413
2456
  either way. -->
2414
- <ng-content select="[strctTagLeading]" />
2415
- <ng-content />
2416
- </span>
2457
+ <ng-content select="[strctTagLeading]" />
2458
+ <ng-content />
2459
+ </span>
2460
+ }
2417
2461
  @if (removable() && !disabled()) {
2418
2462
  <button
2419
2463
  type="button"
@@ -2424,25 +2468,42 @@ class StrctTag {
2424
2468
  <strct-icon name="close" [size]="11" [strokeWidth]="1.6" />
2425
2469
  </button>
2426
2470
  }
2427
- `, isInline: true, styles: [".strct-tag{display:inline-flex;align-items:center;gap:5px;padding-block:3px;padding-inline:9px 4px;border-radius:4px;font-size:12px;font-weight:500;color:var(--t1);background:var(--bg-3);border:1px solid var(--b2)}.strct-tag:not(:has(.strct-tag__remove)){padding-inline-end:9px}.strct-tag--accent{color:var(--acc);border-color:var(--acc30);background:var(--acc-s)}.strct-tag--success{color:var(--success);border-color:var(--success);background:transparent}.strct-tag--warning{color:var(--warning);border-color:var(--warning);background:transparent}.strct-tag--critical{color:var(--critical);border-color:var(--critical);background:transparent}.strct-tag__text{display:inline-flex;align-items:center;gap:5px}.strct-tag--interactive .strct-tag__text{cursor:pointer;border-radius:3px}.strct-tag--interactive:hover{border-color:var(--b3);background:var(--bg-2)}.strct-tag--interactive.strct-tag--accent:hover{border-color:var(--acc)}.strct-tag__text:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}.strct-tag--pill{border-radius:var(--radius-full, 999px);padding-inline:10px 5px}.strct-tag--pill:not(:has(.strct-tag__remove)){padding-inline-end:10px}.strct-tag--pill .strct-tag__remove{border-radius:999px}.strct-tag--mono{font-family:var(--mono)}.strct-tag__remove{display:inline-flex;padding:2px;border:0;border-radius:3px;background:transparent;color:currentColor;opacity:.65;cursor:pointer}.strct-tag__remove:hover,.strct-tag__remove:focus-visible{opacity:1;background:var(--dn)}\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 });
2471
+ `, isInline: true, styles: [".strct-tag{display:inline-flex;align-items:center;gap:5px;padding-block:3px;padding-inline:9px 4px;border-radius:4px;font-size:12px;font-weight:500;color:var(--t1);background:var(--bg-3);border:1px solid var(--b2)}.strct-tag:not(:has(.strct-tag__remove)){padding-inline-end:9px}.strct-tag--accent{color:var(--acc);border-color:var(--acc30);background:var(--acc-s)}.strct-tag--success{color:var(--success);border-color:var(--success);background:transparent}.strct-tag--warning{color:var(--warning);border-color:var(--warning);background:transparent}.strct-tag--critical{color:var(--critical);border-color:var(--critical);background:transparent}.strct-tag__text{display:inline-flex;align-items:center;gap:5px}.strct-tag__text--link{color:inherit;text-decoration:none;cursor:pointer}.strct-tag__text--link:hover{text-decoration:underline}.strct-tag--interactive .strct-tag__text{cursor:pointer;border-radius:3px}.strct-tag--interactive:hover{border-color:var(--b3);background:var(--bg-2)}.strct-tag--interactive.strct-tag--accent:hover{border-color:var(--acc)}.strct-tag__text:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}.strct-tag--pill{border-radius:var(--radius-full, 999px);padding-inline:10px 5px}.strct-tag--pill:not(:has(.strct-tag__remove)){padding-inline-end:10px}.strct-tag--pill .strct-tag__remove{border-radius:999px}.strct-tag--mono{font-family:var(--mono)}.strct-tag__remove{display:inline-flex;padding:2px;border:0;border-radius:3px;background:transparent;color:currentColor;opacity:.65;cursor:pointer}.strct-tag__remove:hover,.strct-tag__remove:focus-visible{opacity:1;background:var(--dn)}\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 });
2428
2472
  }
2429
2473
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctTag, decorators: [{
2430
2474
  type: Component,
2431
2475
  args: [{ selector: 'strct-tag', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
2432
- <span
2433
- class="strct-tag__text"
2434
- [attr.role]="interactive() ? 'button' : null"
2435
- [attr.tabindex]="interactive() && !disabled() ? 0 : null"
2436
- [attr.aria-label]="interactive() && activateLabel() ? activateLabel() : null"
2437
- (click)="onActivate()"
2438
- (keydown.enter)="onActivate()"
2439
- (keydown.space)="$event.preventDefault(); onActivate()"
2440
- >
2441
- <!-- Declared before the catch-all so the selector wins; it renders first
2476
+ <!-- With an href the body is a real link: Ctrl/Cmd-click opens a tab, the
2477
+ address shows on hover and it can be copied as a link — none of which
2478
+ a role="button" can do. The same text, the same focus ring. -->
2479
+ @if (href() && !disabled()) {
2480
+ <a
2481
+ class="strct-tag__text strct-tag__text--link"
2482
+ [attr.href]="href()"
2483
+ [attr.target]="target() || null"
2484
+ [attr.rel]="target() === '_blank' ? 'noopener noreferrer' : null"
2485
+ [attr.aria-label]="activateLabel() || null"
2486
+ (click)="onActivate()"
2487
+ >
2488
+ <ng-content select="[strctTagLeading]" />
2489
+ <ng-content />
2490
+ </a>
2491
+ } @else {
2492
+ <span
2493
+ class="strct-tag__text"
2494
+ [attr.role]="interactive() ? 'button' : null"
2495
+ [attr.tabindex]="interactive() && !disabled() ? 0 : null"
2496
+ [attr.aria-label]="interactive() && activateLabel() ? activateLabel() : null"
2497
+ (click)="onActivate()"
2498
+ (keydown.enter)="onActivate()"
2499
+ (keydown.space)="$event.preventDefault(); onActivate()"
2500
+ >
2501
+ <!-- Declared before the catch-all so the selector wins; it renders first
2442
2502
  either way. -->
2443
- <ng-content select="[strctTagLeading]" />
2444
- <ng-content />
2445
- </span>
2503
+ <ng-content select="[strctTagLeading]" />
2504
+ <ng-content />
2505
+ </span>
2506
+ }
2446
2507
  @if (removable() && !disabled()) {
2447
2508
  <button
2448
2509
  type="button"
@@ -2462,8 +2523,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
2462
2523
  '[class.strct-tag--success]': "status() === 'success'",
2463
2524
  '[class.strct-tag--warning]': "status() === 'warning'",
2464
2525
  '[class.strct-tag--critical]': "status() === 'critical'",
2465
- }, styles: [".strct-tag{display:inline-flex;align-items:center;gap:5px;padding-block:3px;padding-inline:9px 4px;border-radius:4px;font-size:12px;font-weight:500;color:var(--t1);background:var(--bg-3);border:1px solid var(--b2)}.strct-tag:not(:has(.strct-tag__remove)){padding-inline-end:9px}.strct-tag--accent{color:var(--acc);border-color:var(--acc30);background:var(--acc-s)}.strct-tag--success{color:var(--success);border-color:var(--success);background:transparent}.strct-tag--warning{color:var(--warning);border-color:var(--warning);background:transparent}.strct-tag--critical{color:var(--critical);border-color:var(--critical);background:transparent}.strct-tag__text{display:inline-flex;align-items:center;gap:5px}.strct-tag--interactive .strct-tag__text{cursor:pointer;border-radius:3px}.strct-tag--interactive:hover{border-color:var(--b3);background:var(--bg-2)}.strct-tag--interactive.strct-tag--accent:hover{border-color:var(--acc)}.strct-tag__text:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}.strct-tag--pill{border-radius:var(--radius-full, 999px);padding-inline:10px 5px}.strct-tag--pill:not(:has(.strct-tag__remove)){padding-inline-end:10px}.strct-tag--pill .strct-tag__remove{border-radius:999px}.strct-tag--mono{font-family:var(--mono)}.strct-tag__remove{display:inline-flex;padding:2px;border:0;border-radius:3px;background:transparent;color:currentColor;opacity:.65;cursor:pointer}.strct-tag__remove:hover,.strct-tag__remove:focus-visible{opacity:1;background:var(--dn)}\n"] }]
2466
- }], ctorParameters: () => [], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], activateLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "activateLabel", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], mono: [{ type: i0.Input, args: [{ isSignal: true, alias: "mono", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }], activated: [{ type: i0.Output, args: ["activated"] }] } });
2526
+ }, styles: [".strct-tag{display:inline-flex;align-items:center;gap:5px;padding-block:3px;padding-inline:9px 4px;border-radius:4px;font-size:12px;font-weight:500;color:var(--t1);background:var(--bg-3);border:1px solid var(--b2)}.strct-tag:not(:has(.strct-tag__remove)){padding-inline-end:9px}.strct-tag--accent{color:var(--acc);border-color:var(--acc30);background:var(--acc-s)}.strct-tag--success{color:var(--success);border-color:var(--success);background:transparent}.strct-tag--warning{color:var(--warning);border-color:var(--warning);background:transparent}.strct-tag--critical{color:var(--critical);border-color:var(--critical);background:transparent}.strct-tag__text{display:inline-flex;align-items:center;gap:5px}.strct-tag__text--link{color:inherit;text-decoration:none;cursor:pointer}.strct-tag__text--link:hover{text-decoration:underline}.strct-tag--interactive .strct-tag__text{cursor:pointer;border-radius:3px}.strct-tag--interactive:hover{border-color:var(--b3);background:var(--bg-2)}.strct-tag--interactive.strct-tag--accent:hover{border-color:var(--acc)}.strct-tag__text:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}.strct-tag--pill{border-radius:var(--radius-full, 999px);padding-inline:10px 5px}.strct-tag--pill:not(:has(.strct-tag__remove)){padding-inline-end:10px}.strct-tag--pill .strct-tag__remove{border-radius:999px}.strct-tag--mono{font-family:var(--mono)}.strct-tag__remove{display:inline-flex;padding:2px;border:0;border-radius:3px;background:transparent;color:currentColor;opacity:.65;cursor:pointer}.strct-tag__remove:hover,.strct-tag__remove:focus-visible{opacity:1;background:var(--dn)}\n"] }]
2527
+ }], ctorParameters: () => [], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], activateLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "activateLabel", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], mono: [{ type: i0.Input, args: [{ isSignal: true, alias: "mono", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }], activated: [{ type: i0.Output, args: ["activated"] }] } });
2467
2528
 
2468
2529
  /**
2469
2530
  * Circular avatar: an image when `src` is set, otherwise initials from `name`.
@@ -4996,6 +5057,15 @@ class StrctModal {
4996
5057
  * `dismissible` for lightweight, transient dialogs where quick dismissal is fine.
4997
5058
  */
4998
5059
  dismissible = input(false, { ...(ngDevMode ? { debugName: "dismissible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5060
+ /**
5061
+ * Escape closes the dialog. Defaults to `dismissible`; set it on its own for
5062
+ * a window that should close on Escape — nothing is lost — but not on a
5063
+ * stray click outside it. An Escape a focused control already handled
5064
+ * (`defaultPrevented`) is left alone.
5065
+ */
5066
+ closeOnEscape = input(null, { ...(ngDevMode ? { debugName: "closeOnEscape" } : /* istanbul ignore next */ {}), transform: (v) => (v === null || v === undefined ? null : booleanAttribute(v)) });
5067
+ /** A click on the backdrop closes it. Defaults to `dismissible`. */
5068
+ closeOnBackdrop = input(null, { ...(ngDevMode ? { debugName: "closeOnBackdrop" } : /* istanbul ignore next */ {}), transform: (v) => (v === null || v === undefined ? null : booleanAttribute(v)) });
4999
5069
  /** Allow repositioning the dialog by dragging its header (mouse + touch). */
5000
5070
  draggable = input(false, { ...(ngDevMode ? { debugName: "draggable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5001
5071
  /** Extra class(es) added to the dialog panel — style it from app-global CSS. */
@@ -5073,12 +5143,17 @@ class StrctModal {
5073
5143
  // that bubbled up from a child (e.g. a space typed in an input/textarea).
5074
5144
  if (event && event.target !== event.currentTarget)
5075
5145
  return;
5076
- if (this.dismissible())
5146
+ if (this.closeOnBackdrop() ?? this.dismissible())
5077
5147
  this.close();
5078
5148
  }
5079
- onEscape() {
5149
+ onEscape(event) {
5150
+ // A control that used Escape first keeps it: an IME composition, a
5151
+ // searchbox that clears. Both mark the event handled.
5152
+ if (event?.defaultPrevented)
5153
+ return;
5080
5154
  // With several modals open, only the topmost one reacts to Escape.
5081
- if (this.open() && this.dismissible() && isTopmostModal(this))
5155
+ const allowed = this.closeOnEscape() ?? this.dismissible();
5156
+ if (this.open() && allowed && isTopmostModal(this))
5082
5157
  this.close();
5083
5158
  }
5084
5159
  /** Wrap Tab focus within the dialog. */
@@ -5167,7 +5242,7 @@ class StrctModal {
5167
5242
  (items[0] ?? this.dialog())?.focus();
5168
5243
  }
5169
5244
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
5170
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctModal, isStandalone: true, selector: "strct-modal", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, hideFooter: { classPropertyName: "hideFooter", publicName: "hideFooter", isSignal: true, isRequired: false, transformFunction: null }, chromeless: { classPropertyName: "chromeless", publicName: "chromeless", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, draggable: { classPropertyName: "draggable", publicName: "draggable", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, backdropClass: { classPropertyName: "backdropClass", publicName: "backdropClass", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, queries: [{ propertyName: "lazyContent", first: true, predicate: StrctModalContent, descendants: true, isSignal: true }], ngImport: i0, template: `
5245
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctModal, isStandalone: true, selector: "strct-modal", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, hideFooter: { classPropertyName: "hideFooter", publicName: "hideFooter", isSignal: true, isRequired: false, transformFunction: null }, chromeless: { classPropertyName: "chromeless", publicName: "chromeless", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdrop: { classPropertyName: "closeOnBackdrop", publicName: "closeOnBackdrop", isSignal: true, isRequired: false, transformFunction: null }, draggable: { classPropertyName: "draggable", publicName: "draggable", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, backdropClass: { classPropertyName: "backdropClass", publicName: "backdropClass", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape($event)" } }, queries: [{ propertyName: "lazyContent", first: true, predicate: StrctModalContent, descendants: true, isSignal: true }], ngImport: i0, template: `
5171
5246
  @if (open()) {
5172
5247
  <!-- Backdrop: pointer-only dismiss target. No role/tabindex — keyboard users
5173
5248
  dismiss via Escape, and the focus trap makes the backdrop unreachable
@@ -5288,9 +5363,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
5288
5363
  </div>
5289
5364
  }
5290
5365
  `, host: {
5291
- '(document:keydown.escape)': 'onEscape()',
5366
+ '(document:keydown.escape)': 'onEscape($event)',
5292
5367
  }, styles: [".strct-modal__overlay{position:fixed;inset:0;z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;padding:var(--space-5);background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-modal-fade .12s ease}@media(max-width:768px){.strct-modal__overlay{padding:var(--space-3)}}.strct-modal__dialog{width:100%;max-height:calc(100vh - 48px);display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-elevated);overflow:hidden;animation:strct-modal-rise .14s ease}.strct-modal__dialog--sm{max-width:min(480px,calc(100vw - 32px))}.strct-modal__dialog--md{max-width:min(640px,calc(100vw - 32px))}.strct-modal__dialog--lg{max-width:min(860px,calc(100vw - 32px))}.strct-modal__dialog--xl{max-width:min(1080px,calc(100vw - 32px))}.strct-modal__head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--b1)}.strct-modal__head--drag{cursor:move;-webkit-user-select:none;user-select:none;touch-action:none}.strct-modal__overlay--glass{background:#00000061;-webkit-backdrop-filter:blur(10px) saturate(1.25);backdrop-filter:blur(10px) saturate(1.25)}.strct-modal__dialog--glass{background:color-mix(in srgb,var(--bg-1) 72%,transparent);-webkit-backdrop-filter:blur(24px) saturate(1.4);backdrop-filter:blur(24px) saturate(1.4)}.strct-modal__dialog--glass .strct-modal__foot{background:color-mix(in srgb,var(--bg-2) 55%,transparent)}.strct-modal__title{font-size:14px;font-weight:600;color:var(--t1)}.strct-modal__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-modal__close:hover{color:var(--t1);background:var(--bg-3)}.strct-modal__body{padding:var(--space-4);overflow-y:auto;color:var(--t2);font-size:13px}.strct-modal__dialog--chromeless{width:calc(232px + var(--strct-wiz-content-min, 864px) + 2px);max-width:calc(100vw - 32px)}.strct-modal__dialog--chromeless:has(.strct-wiz__layout--aside){width:calc(232px + var(--strct-wiz-content-min, 864px) + 280px + 2px)}.strct-modal__dialog--chromeless .strct-modal__body{padding:0;overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0}.strct-modal__dialog--chromeless .strct-modal__body>*{flex:1;min-height:0}.strct-modal__foot{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);padding:var(--space-3) var(--space-4);border-top:1px solid var(--b1);background:var(--bg-2)}@keyframes strct-modal-fade{0%{opacity:0}}@keyframes strct-modal-rise{0%{opacity:0;transform:translateY(8px) scale(.98)}}@media(prefers-reduced-motion:reduce){.strct-modal__overlay,.strct-modal__dialog{animation:none}}\n"] }]
5293
- }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hideFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideFooter", required: false }] }], chromeless: [{ type: i0.Input, args: [{ isSignal: true, alias: "chromeless", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], draggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "draggable", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], backdropClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdropClass", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], lazyContent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctModalContent), { isSignal: true }] }] } });
5368
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hideFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideFooter", required: false }] }], chromeless: [{ type: i0.Input, args: [{ isSignal: true, alias: "chromeless", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], closeOnBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdrop", required: false }] }], draggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "draggable", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], backdropClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdropClass", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], lazyContent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctModalContent), { isSignal: true }] }] } });
5294
5369
 
5295
5370
  /** Marks the drawer's footer action area: `<ng-container strctDrawerFooter>…`. */
5296
5371
  class StrctDrawerFooter {
@@ -10001,7 +10076,7 @@ class StrctChips {
10001
10076
  (blur)="commitDraft(); onTouched()"
10002
10077
  />
10003
10078
  </div>
10004
- `, isInline: true, styles: [".strct-chips{display:flex;flex-wrap:wrap;align-items:center;gap:6px;width:100%;min-height:38px;padding:5px 8px;background:var(--bg-2);border:1px solid var(--b2);border-radius:6px;cursor:text;transition:border-color .14s ease,box-shadow .14s ease}.strct-chips:focus-within{border-color:var(--acc50);box-shadow:0 0 0 3px var(--acc18);background:var(--bg-1)}.strct-chips__input{flex:1;min-width:80px;border:0;outline:none;background:transparent;font-family:var(--font);font-size:13px;color:var(--t1);padding:3px 2px}.strct-chips__input::placeholder{color:var(--t3)}\n"], dependencies: [{ kind: "component", type: StrctTag, selector: "strct-tag", inputs: ["status", "removable", "disabled", "removeLabel", "activateLabel", "interactive", "shape", "mono"], outputs: ["removed", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10079
+ `, isInline: true, styles: [".strct-chips{display:flex;flex-wrap:wrap;align-items:center;gap:6px;width:100%;min-height:38px;padding:5px 8px;background:var(--bg-2);border:1px solid var(--b2);border-radius:6px;cursor:text;transition:border-color .14s ease,box-shadow .14s ease}.strct-chips:focus-within{border-color:var(--acc50);box-shadow:0 0 0 3px var(--acc18);background:var(--bg-1)}.strct-chips__input{flex:1;min-width:80px;border:0;outline:none;background:transparent;font-family:var(--font);font-size:13px;color:var(--t1);padding:3px 2px}.strct-chips__input::placeholder{color:var(--t3)}\n"], dependencies: [{ kind: "component", type: StrctTag, selector: "strct-tag", inputs: ["status", "removable", "disabled", "removeLabel", "activateLabel", "href", "target", "interactive", "shape", "mono"], outputs: ["removed", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10005
10080
  }
10006
10081
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctChips, decorators: [{
10007
10082
  type: Component,
@@ -25542,6 +25617,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
25542
25617
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], restoreLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "restoreLabel", required: false }] }], itemTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctWindowDockItem), { ...{ read: TemplateRef }, isSignal: true }] }], restoreMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "restoreMode", required: false }] }], restoreRequest: [{ type: i0.Output, args: ["restoreRequest"] }] } });
25543
25618
 
25544
25619
  /** A card the message carries — an action to approve, a result to read. */
25620
+ /** A quiet row under the bubble — copy, a status, a fold. */
25621
+ class StrctChatMessageActions {
25622
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctChatMessageActions, deps: [], target: i0.ɵɵFactoryTarget.Directive });
25623
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctChatMessageActions, isStandalone: true, selector: "[strctChatMessageActions]", ngImport: i0 });
25624
+ }
25625
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctChatMessageActions, decorators: [{
25626
+ type: Directive,
25627
+ args: [{ selector: '[strctChatMessageActions]' }]
25628
+ }] });
25545
25629
  class StrctChatAttachment {
25546
25630
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctChatAttachment, deps: [], target: i0.ɵɵFactoryTarget.Directive });
25547
25631
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctChatAttachment, isStandalone: true, selector: "[strctChatAttachment]", host: { classAttribute: "strct-chat__attachment" }, ngImport: i0 });
@@ -25710,8 +25794,14 @@ class StrctChatMessage {
25710
25794
  }
25711
25795
  </div>
25712
25796
  <ng-content select="[strctChatAttachment]" />
25797
+ <!-- Copy, a status ("Stopped", "Model call failed"), a quiet "Used 3
25798
+ tools" fold. It was going into the attachment slot, which the
25799
+ library documents as a card the message carries. -->
25800
+ <div class="strct-msg__actions">
25801
+ <ng-content select="[strctChatMessageActions]" />
25802
+ </div>
25713
25803
  </div>
25714
- `, isInline: true, styles: [".strct-msg{display:flex;align-items:flex-start;gap:var(--space-2);min-width:0}.strct-msg[data-author=user]{flex-direction:row-reverse}.strct-msg[data-author=system]{justify-content:center}.strct-msg__avatar{flex:none}.strct-msg__body{display:flex;flex-direction:column;gap:4px;min-width:0;max-width:80%}.strct-msg[data-author=user] .strct-msg__body{align-items:flex-end}.strct-msg__meta{display:flex;align-items:baseline;gap:6px;font-size:var(--text-sm);color:var(--t3)}.strct-msg__name{font-weight:600}.strct-msg__bubble{padding:var(--space-2) var(--space-3);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);color:var(--t1);font-size:13px;line-height:1.5;min-width:0;overflow-wrap:anywhere}.strct-msg[data-author=user] .strct-msg__bubble{background:var(--acc-s);border-color:var(--acc30)}.strct-msg[data-author=system] .strct-msg__bubble{border:0;background:none;color:var(--t3);font-size:var(--text-sm);text-align:center}.strct-msg__caret{display:inline-block;width:2px;height:1em;margin-inline-start:2px;vertical-align:text-bottom;background:var(--acc);animation:strct-chat-caret 1s steps(2,start) infinite}@keyframes strct-chat-caret{to{opacity:0}}@media(prefers-reduced-motion:reduce){.strct-msg__caret{animation:none}}.strct-chat__attachment{display:block;margin-block-start:4px}\n"], dependencies: [{ kind: "component", type: StrctAvatar, selector: "strct-avatar", inputs: ["icon", "shape", "tone", "src", "name", "size", "status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
25804
+ `, isInline: true, styles: [".strct-msg{display:flex;align-items:flex-start;gap:var(--space-2);min-width:0}.strct-msg[data-author=user]{flex-direction:row-reverse}.strct-msg[data-author=system]{justify-content:center}.strct-msg__avatar{flex:none}.strct-msg__body{display:flex;flex-direction:column;gap:4px;min-width:0;max-width:80%}.strct-msg[data-author=user] .strct-msg__body{align-items:flex-end}.strct-msg__meta{display:flex;align-items:baseline;gap:6px;font-size:var(--text-sm);color:var(--t3)}.strct-msg__name{font-weight:600}.strct-msg__actions:empty{display:none}.strct-msg__actions{display:flex;align-items:center;gap:var(--space-2);margin-block-start:4px;font-size:var(--text-sm);color:var(--t3)}.strct-msg__bubble{padding:var(--space-2) var(--space-3);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);color:var(--t1);font-size:13px;line-height:1.5;min-width:0;overflow-wrap:anywhere}.strct-msg[data-author=user] .strct-msg__bubble{background:var(--acc-s);border-color:var(--acc30)}.strct-msg[data-author=system] .strct-msg__bubble{border:0;background:none;color:var(--t3);font-size:var(--text-sm);text-align:center}.strct-msg__caret{display:inline-block;width:2px;height:1em;margin-inline-start:2px;vertical-align:text-bottom;background:var(--acc);animation:strct-chat-caret 1s steps(2,start) infinite}@keyframes strct-chat-caret{to{opacity:0}}@media(prefers-reduced-motion:reduce){.strct-msg__caret{animation:none}}.strct-chat__attachment{display:block;margin-block-start:4px}\n"], dependencies: [{ kind: "component", type: StrctAvatar, selector: "strct-avatar", inputs: ["icon", "shape", "tone", "src", "name", "size", "status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
25715
25805
  }
25716
25806
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctChatMessage, decorators: [{
25717
25807
  type: Component,
@@ -25743,6 +25833,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
25743
25833
  }
25744
25834
  </div>
25745
25835
  <ng-content select="[strctChatAttachment]" />
25836
+ <!-- Copy, a status ("Stopped", "Model call failed"), a quiet "Used 3
25837
+ tools" fold. It was going into the attachment slot, which the
25838
+ library documents as a card the message carries. -->
25839
+ <div class="strct-msg__actions">
25840
+ <ng-content select="[strctChatMessageActions]" />
25841
+ </div>
25746
25842
  </div>
25747
25843
  `, host: {
25748
25844
  class: 'strct-msg',
@@ -25751,7 +25847,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
25751
25847
  '[attr.aria-label]': 'displayName()',
25752
25848
  // A streaming reply is announced when it finishes, not token by token.
25753
25849
  '[attr.aria-busy]': 'streaming() ? "true" : null',
25754
- }, styles: [".strct-msg{display:flex;align-items:flex-start;gap:var(--space-2);min-width:0}.strct-msg[data-author=user]{flex-direction:row-reverse}.strct-msg[data-author=system]{justify-content:center}.strct-msg__avatar{flex:none}.strct-msg__body{display:flex;flex-direction:column;gap:4px;min-width:0;max-width:80%}.strct-msg[data-author=user] .strct-msg__body{align-items:flex-end}.strct-msg__meta{display:flex;align-items:baseline;gap:6px;font-size:var(--text-sm);color:var(--t3)}.strct-msg__name{font-weight:600}.strct-msg__bubble{padding:var(--space-2) var(--space-3);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);color:var(--t1);font-size:13px;line-height:1.5;min-width:0;overflow-wrap:anywhere}.strct-msg[data-author=user] .strct-msg__bubble{background:var(--acc-s);border-color:var(--acc30)}.strct-msg[data-author=system] .strct-msg__bubble{border:0;background:none;color:var(--t3);font-size:var(--text-sm);text-align:center}.strct-msg__caret{display:inline-block;width:2px;height:1em;margin-inline-start:2px;vertical-align:text-bottom;background:var(--acc);animation:strct-chat-caret 1s steps(2,start) infinite}@keyframes strct-chat-caret{to{opacity:0}}@media(prefers-reduced-motion:reduce){.strct-msg__caret{animation:none}}.strct-chat__attachment{display:block;margin-block-start:4px}\n"] }]
25850
+ }, styles: [".strct-msg{display:flex;align-items:flex-start;gap:var(--space-2);min-width:0}.strct-msg[data-author=user]{flex-direction:row-reverse}.strct-msg[data-author=system]{justify-content:center}.strct-msg__avatar{flex:none}.strct-msg__body{display:flex;flex-direction:column;gap:4px;min-width:0;max-width:80%}.strct-msg[data-author=user] .strct-msg__body{align-items:flex-end}.strct-msg__meta{display:flex;align-items:baseline;gap:6px;font-size:var(--text-sm);color:var(--t3)}.strct-msg__name{font-weight:600}.strct-msg__actions:empty{display:none}.strct-msg__actions{display:flex;align-items:center;gap:var(--space-2);margin-block-start:4px;font-size:var(--text-sm);color:var(--t3)}.strct-msg__bubble{padding:var(--space-2) var(--space-3);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);color:var(--t1);font-size:13px;line-height:1.5;min-width:0;overflow-wrap:anywhere}.strct-msg[data-author=user] .strct-msg__bubble{background:var(--acc-s);border-color:var(--acc30)}.strct-msg[data-author=system] .strct-msg__bubble{border:0;background:none;color:var(--t3);font-size:var(--text-sm);text-align:center}.strct-msg__caret{display:inline-block;width:2px;height:1em;margin-inline-start:2px;vertical-align:text-bottom;background:var(--acc);animation:strct-chat-caret 1s steps(2,start) infinite}@keyframes strct-chat-caret{to{opacity:0}}@media(prefers-reduced-motion:reduce){.strct-msg__caret{animation:none}}.strct-chat__attachment{display:block;margin-block-start:4px}\n"] }]
25755
25851
  }], propDecorators: { author: [{ type: i0.Input, args: [{ isSignal: true, alias: "author", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], avatarIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIcon", required: false }] }], streaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "streaming", required: false }] }], time: [{ type: i0.Input, args: [{ isSignal: true, alias: "time", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], authorNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "authorNames", required: false }] }] } });
25756
25852
  /**
25757
25853
  * The box the user writes in: it grows with the text up to `maxRows`, sends on
@@ -25775,6 +25871,17 @@ class StrctChatComposer {
25775
25871
  * while a reply is still streaming. `disabled` stops the typing as well.
25776
25872
  */
25777
25873
  sendDisabled = input(false, { ...(ngDevMode ? { debugName: "sendDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
25874
+ /**
25875
+ * A reply is streaming: the one button becomes Stop, where every chat the
25876
+ * operators know puts it. It is enabled whatever the draft holds, and Enter
25877
+ * still never sends — the draft stays editable meanwhile.
25878
+ */
25879
+ busy = input(false, { ...(ngDevMode ? { debugName: "busy" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
25880
+ /** What the Stop button is called. */
25881
+ stopLabel = input('Stop', /* @ts-ignore */
25882
+ ...(ngDevMode ? [{ debugName: "stopLabel" }] : /* istanbul ignore next */ []));
25883
+ /** The reader asked for the reply to stop. */
25884
+ stop = output();
25778
25885
  /** How tall the box grows before it scrolls. */
25779
25886
  maxRows = input(8, /* @ts-ignore */
25780
25887
  ...(ngDevMode ? [{ debugName: "maxRows" }] : /* istanbul ignore next */ []));
@@ -25807,7 +25914,7 @@ class StrctChatComposer {
25807
25914
  }
25808
25915
  submit() {
25809
25916
  const text = this.value().trim();
25810
- if (!text || this.disabled() || this.sendDisabled())
25917
+ if (!text || this.disabled() || this.sendDisabled() || this.busy())
25811
25918
  return;
25812
25919
  this.send.emit(text);
25813
25920
  this.value.set('');
@@ -25819,7 +25926,7 @@ class StrctChatComposer {
25819
25926
  }
25820
25927
  }
25821
25928
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctChatComposer, deps: [], target: i0.ɵɵFactoryTarget.Component });
25822
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctChatComposer, isStandalone: true, selector: "strct-chat-composer", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, sendDisabled: { classPropertyName: "sendDisabled", publicName: "sendDisabled", isSignal: true, isRequired: false, transformFunction: null }, maxRows: { classPropertyName: "maxRows", publicName: "maxRows", isSignal: true, isRequired: false, transformFunction: null }, sendLabel: { classPropertyName: "sendLabel", publicName: "sendLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", send: "send" }, host: { classAttribute: "strct-composer" }, ngImport: i0, template: `
25929
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctChatComposer, isStandalone: true, selector: "strct-chat-composer", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, sendDisabled: { classPropertyName: "sendDisabled", publicName: "sendDisabled", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, stopLabel: { classPropertyName: "stopLabel", publicName: "stopLabel", isSignal: true, isRequired: false, transformFunction: null }, maxRows: { classPropertyName: "maxRows", publicName: "maxRows", isSignal: true, isRequired: false, transformFunction: null }, sendLabel: { classPropertyName: "sendLabel", publicName: "sendLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", stop: "stop", send: "send" }, host: { classAttribute: "strct-composer" }, ngImport: i0, template: `
25823
25930
  <label class="strct-composer__label" [attr.for]="inputId">{{ label() }}</label>
25824
25931
  <div class="strct-composer__box">
25825
25932
  <textarea
@@ -25838,14 +25945,19 @@ class StrctChatComposer {
25838
25945
  <button
25839
25946
  type="button"
25840
25947
  class="strct-composer__send"
25841
- [attr.aria-label]="sendLabel()"
25842
- [disabled]="disabled() || sendDisabled() || !value().trim()"
25843
- (click)="submit()"
25948
+ [class.strct-composer__send--stop]="busy()"
25949
+ [attr.aria-label]="busy() ? stopLabel() : sendLabel()"
25950
+ [disabled]="busy() ? disabled() : disabled() || sendDisabled() || !value().trim()"
25951
+ (click)="busy() ? stop.emit() : submit()"
25844
25952
  >
25845
- <strct-icon strictName="arrowUp" [size]="15" [strokeWidth]="1.8" />
25953
+ @if (busy()) {
25954
+ <span class="strct-composer__stopmark" aria-hidden="true"></span>
25955
+ } @else {
25956
+ <strct-icon strictName="arrowUp" [size]="15" [strokeWidth]="1.8" />
25957
+ }
25846
25958
  </button>
25847
25959
  </div>
25848
- `, isInline: true, styles: [".strct-composer{display:block}.strct-composer__label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-composer__box{display:flex;align-items:flex-end;gap:4px;padding:4px 4px 4px var(--space-3);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);transition:border-color .14s ease,box-shadow .14s ease}.strct-composer__box:focus-within{border-color:var(--acc50);box-shadow:0 0 0 3px var(--acc18);background:var(--bg-1)}.strct-composer__input{flex:1;min-width:0;border:0;background:none;resize:none;padding:6px 0;font-family:var(--font);font-size:13px;line-height:20px;color:var(--t1);overflow-y:auto}.strct-composer__input:focus{outline:none}.strct-composer__input::placeholder{color:var(--t3)}.strct-composer__send{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border:0;border-radius:var(--radius-md);background:var(--acc);color:var(--inv);cursor:pointer}.strct-composer__send:disabled{opacity:var(--disabled-opacity);cursor:not-allowed}.strct-composer__send:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}\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 });
25960
+ `, isInline: true, styles: [".strct-composer{display:block}.strct-composer__label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-composer__box{display:flex;align-items:flex-end;gap:4px;padding:4px 4px 4px var(--space-3);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);transition:border-color .14s ease,box-shadow .14s ease}.strct-composer__box:focus-within{border-color:var(--acc50);box-shadow:0 0 0 3px var(--acc18);background:var(--bg-1)}.strct-composer__input{flex:1;min-width:0;border:0;background:none;resize:none;padding:6px 0;font-family:var(--font);font-size:13px;line-height:20px;color:var(--t1);overflow-y:auto}.strct-composer__input:focus{outline:none}.strct-composer__input::placeholder{color:var(--t3)}.strct-composer__send{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border:0;border-radius:var(--radius-md);background:var(--acc);color:var(--inv);cursor:pointer}.strct-composer__stopmark{width:10px;height:10px;border-radius:2px;background:currentColor}.strct-composer__send:disabled{opacity:var(--disabled-opacity);cursor:not-allowed}.strct-composer__send:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}\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 });
25849
25961
  }
25850
25962
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctChatComposer, decorators: [{
25851
25963
  type: Component,
@@ -25868,15 +25980,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
25868
25980
  <button
25869
25981
  type="button"
25870
25982
  class="strct-composer__send"
25871
- [attr.aria-label]="sendLabel()"
25872
- [disabled]="disabled() || sendDisabled() || !value().trim()"
25873
- (click)="submit()"
25983
+ [class.strct-composer__send--stop]="busy()"
25984
+ [attr.aria-label]="busy() ? stopLabel() : sendLabel()"
25985
+ [disabled]="busy() ? disabled() : disabled() || sendDisabled() || !value().trim()"
25986
+ (click)="busy() ? stop.emit() : submit()"
25874
25987
  >
25875
- <strct-icon strictName="arrowUp" [size]="15" [strokeWidth]="1.8" />
25988
+ @if (busy()) {
25989
+ <span class="strct-composer__stopmark" aria-hidden="true"></span>
25990
+ } @else {
25991
+ <strct-icon strictName="arrowUp" [size]="15" [strokeWidth]="1.8" />
25992
+ }
25876
25993
  </button>
25877
25994
  </div>
25878
- `, host: { class: 'strct-composer' }, styles: [".strct-composer{display:block}.strct-composer__label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-composer__box{display:flex;align-items:flex-end;gap:4px;padding:4px 4px 4px var(--space-3);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);transition:border-color .14s ease,box-shadow .14s ease}.strct-composer__box:focus-within{border-color:var(--acc50);box-shadow:0 0 0 3px var(--acc18);background:var(--bg-1)}.strct-composer__input{flex:1;min-width:0;border:0;background:none;resize:none;padding:6px 0;font-family:var(--font);font-size:13px;line-height:20px;color:var(--t1);overflow-y:auto}.strct-composer__input:focus{outline:none}.strct-composer__input::placeholder{color:var(--t3)}.strct-composer__send{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border:0;border-radius:var(--radius-md);background:var(--acc);color:var(--inv);cursor:pointer}.strct-composer__send:disabled{opacity:var(--disabled-opacity);cursor:not-allowed}.strct-composer__send:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}\n"] }]
25879
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], sendDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendDisabled", required: false }] }], maxRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRows", required: false }] }], sendLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendLabel", required: false }] }], send: [{ type: i0.Output, args: ["send"] }] } });
25995
+ `, host: { class: 'strct-composer' }, styles: [".strct-composer{display:block}.strct-composer__label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-composer__box{display:flex;align-items:flex-end;gap:4px;padding:4px 4px 4px var(--space-3);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-2);transition:border-color .14s ease,box-shadow .14s ease}.strct-composer__box:focus-within{border-color:var(--acc50);box-shadow:0 0 0 3px var(--acc18);background:var(--bg-1)}.strct-composer__input{flex:1;min-width:0;border:0;background:none;resize:none;padding:6px 0;font-family:var(--font);font-size:13px;line-height:20px;color:var(--t1);overflow-y:auto}.strct-composer__input:focus{outline:none}.strct-composer__input::placeholder{color:var(--t3)}.strct-composer__send{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border:0;border-radius:var(--radius-md);background:var(--acc);color:var(--inv);cursor:pointer}.strct-composer__stopmark{width:10px;height:10px;border-radius:2px;background:currentColor}.strct-composer__send:disabled{opacity:var(--disabled-opacity);cursor:not-allowed}.strct-composer__send:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}\n"] }]
25996
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], sendDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendDisabled", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], stopLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "stopLabel", required: false }] }], stop: [{ type: i0.Output, args: ["stop"] }], maxRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRows", required: false }] }], sendLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendLabel", required: false }] }], send: [{ type: i0.Output, args: ["send"] }] } });
25880
25997
  let composerCounter = 0;
25881
25998
 
25882
25999
  /**
@@ -26704,7 +26821,7 @@ class StrctConfirmOutlet {
26704
26821
  </ng-container>
26705
26822
  }
26706
26823
  </strct-modal>
26707
- `, isInline: true, styles: [".strct-confirm__message{margin:0;color:var(--t2)}\n"], dependencies: [{ kind: "component", type: StrctModal, selector: "strct-modal", inputs: ["open", "title", "size", "hideFooter", "chromeless", "closeLabel", "dismissible", "draggable", "panelClass", "backdropClass", "variant"], outputs: ["openChange", "closed"] }, { kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
26824
+ `, isInline: true, styles: [".strct-confirm__message{margin:0;color:var(--t2)}\n"], dependencies: [{ kind: "component", type: StrctModal, selector: "strct-modal", inputs: ["open", "title", "size", "hideFooter", "chromeless", "closeLabel", "dismissible", "closeOnEscape", "closeOnBackdrop", "draggable", "panelClass", "backdropClass", "variant"], outputs: ["openChange", "closed"] }, { kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
26708
26825
  }
26709
26826
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctConfirmOutlet, decorators: [{
26710
26827
  type: Component,
@@ -28294,6 +28411,1035 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
28294
28411
  `, host: { class: 'strct-heatmap' }, styles: [".strct-heatmap{display:block}.strct-heatmap__svg{width:100%;display:block}.strct-heatmap__empty{display:flex;align-items:center;justify-content:center;min-height:60px;font-size:12px;color:var(--t3)}.strct-heatmap__label{fill:var(--t3);font-size:10px}.strct-heatmap__label--row{font-family:var(--font)}.strct-heatmap__label--col{font-family:var(--mono);font-variant-numeric:tabular-nums}.strct-heatmap__cell{stroke:var(--bg-1);stroke-width:1}@media(prefers-reduced-motion:no-preference){.strct-heatmap__cell{transition:opacity .12s ease}}.strct-heatmap__cell:hover{opacity:.75}\n"] }]
28295
28412
  }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], cols: [{ type: i0.Input, args: [{ isSignal: true, alias: "cols", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], colLabelEvery: [{ type: i0.Input, args: [{ isSignal: true, alias: "colLabelEvery", required: false }] }], colLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "colLabel", required: false }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], thresholds: [{ type: i0.Input, args: [{ isSignal: true, alias: "thresholds", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], summaryFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "summaryFormat", required: false }] }], cellHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellHeight", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], rowLabelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowLabelWidth", required: false }] }] } });
28296
28413
 
28414
+ /**
28415
+ * A Markdown reader that produces plain objects — never HTML.
28416
+ *
28417
+ * A model's reply is Markdown, and an app that reaches for a Markdown library
28418
+ * ends up handing the browser a string of HTML (`innerHTML` plus a sanitiser),
28419
+ * which a sealed, reviewed build cannot accept. This reader returns a tree the
28420
+ * templates draw with ordinary bindings, so every word reaches the page as a
28421
+ * text node and raw HTML is shown as the characters it is.
28422
+ *
28423
+ * It is deliberately a subset of CommonMark + GFM: paragraphs, ATX headings,
28424
+ * fenced code, lists (nested), block quotes, thematic breaks, tables with
28425
+ * alignment, and inline emphasis / strong / strikethrough / code / links /
28426
+ * autolinks. Everything else is text.
28427
+ *
28428
+ * Every loop is bounded, because the input is a remote model's output.
28429
+ */
28430
+ /** Hard limits, so hostile input costs a known amount of work. */
28431
+ const STRCT_MD_LIMITS = {
28432
+ /** Characters of source read at all. */
28433
+ source: 256 * 1024,
28434
+ /** Lines read at all. */
28435
+ lines: 20000,
28436
+ /** How deep lists and quotes may nest. */
28437
+ depth: 8,
28438
+ /** Inline delimiter runs considered per text span. */
28439
+ delimiters: 2000,
28440
+ };
28441
+ const RULE = /^ {0,3}((\*\s*){3,}|(-\s*){3,}|(_\s*){3,})$/;
28442
+ const ATX = /^ {0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
28443
+ const FENCE = /^ {0,3}(`{3,}|~{3,})\s*([^`]*)$/;
28444
+ const UL = /^(\s*)([-*+])\s+(.*)$/;
28445
+ const OL = /^(\s*)(\d{1,9})[.)]\s+(.*)$/;
28446
+ const QUOTE = /^ {0,3}>\s?(.*)$/;
28447
+ const TABLE_DELIM = /^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/;
28448
+ /**
28449
+ * Read Markdown into blocks.
28450
+ *
28451
+ * `streaming` says the source may be cut mid-token: an unterminated fence is
28452
+ * still a code block, so a reply that streams code looks like code from its
28453
+ * first line rather than flashing as a paragraph and then becoming one.
28454
+ */
28455
+ function strctParseMarkdown(source, options = {}) {
28456
+ const text = source.length > STRCT_MD_LIMITS.source ? source.slice(0, STRCT_MD_LIMITS.source) : source;
28457
+ const lines = text.replace(/\r\n?/g, '\n').split('\n').slice(0, STRCT_MD_LIMITS.lines);
28458
+ return parseBlocks(lines, 0, options);
28459
+ }
28460
+ function parseBlocks(lines, depth, options) {
28461
+ const out = [];
28462
+ let i = 0;
28463
+ const paragraph = [];
28464
+ const flushParagraph = () => {
28465
+ if (!paragraph.length)
28466
+ return;
28467
+ out.push({ kind: 'p', inlines: parseInlines(paragraph.join('\n'), options) });
28468
+ paragraph.length = 0;
28469
+ };
28470
+ while (i < lines.length) {
28471
+ const line = lines[i];
28472
+ // Blank line: ends a paragraph, nothing else.
28473
+ if (!line.trim()) {
28474
+ flushParagraph();
28475
+ i++;
28476
+ continue;
28477
+ }
28478
+ // Fenced code. An unclosed fence is still code while streaming.
28479
+ const fence = FENCE.exec(line);
28480
+ if (fence) {
28481
+ flushParagraph();
28482
+ const marker = fence[1][0];
28483
+ const width = fence[1].length;
28484
+ const lang = fence[2].trim();
28485
+ const body = [];
28486
+ let closed = false;
28487
+ i++;
28488
+ while (i < lines.length) {
28489
+ const candidate = lines[i];
28490
+ const end = new RegExp(`^ {0,3}${marker === '`' ? '`' : '~'}{${width},}\\s*$`).test(candidate);
28491
+ if (end) {
28492
+ closed = true;
28493
+ i++;
28494
+ break;
28495
+ }
28496
+ body.push(candidate);
28497
+ i++;
28498
+ }
28499
+ out.push({ kind: 'code', text: body.join('\n'), lang, closed });
28500
+ continue;
28501
+ }
28502
+ // Thematic break.
28503
+ if (RULE.test(line)) {
28504
+ flushParagraph();
28505
+ out.push({ kind: 'rule' });
28506
+ i++;
28507
+ continue;
28508
+ }
28509
+ // ATX heading.
28510
+ const atx = ATX.exec(line);
28511
+ if (atx) {
28512
+ flushParagraph();
28513
+ out.push({
28514
+ kind: 'heading',
28515
+ level: atx[1].length,
28516
+ inlines: parseInlines(atx[2], options),
28517
+ });
28518
+ i++;
28519
+ continue;
28520
+ }
28521
+ // Block quote: take the run of quoted lines and read them as blocks.
28522
+ const quote = QUOTE.exec(line);
28523
+ if (quote && depth < STRCT_MD_LIMITS.depth) {
28524
+ flushParagraph();
28525
+ const inner = [];
28526
+ while (i < lines.length) {
28527
+ const m = QUOTE.exec(lines[i]);
28528
+ if (m) {
28529
+ inner.push(m[1]);
28530
+ i++;
28531
+ }
28532
+ else if (lines[i].trim() && inner.length) {
28533
+ // A lazy continuation line belongs to the quote's last paragraph.
28534
+ inner.push(lines[i]);
28535
+ i++;
28536
+ }
28537
+ else
28538
+ break;
28539
+ }
28540
+ out.push({ kind: 'quote', blocks: parseBlocks(inner, depth + 1, options) });
28541
+ continue;
28542
+ }
28543
+ // Table: a header row followed by a delimiter row.
28544
+ if (line.includes('|') && i + 1 < lines.length && TABLE_DELIM.test(lines[i + 1])) {
28545
+ flushParagraph();
28546
+ const head = splitRow(lines[i]).map((c) => parseInlines(c, options));
28547
+ const align = splitRow(lines[i + 1]).map(toAlign);
28548
+ const rows = [];
28549
+ i += 2;
28550
+ while (i < lines.length && lines[i].includes('|') && lines[i].trim()) {
28551
+ rows.push(splitRow(lines[i]).map((c) => parseInlines(c, options)));
28552
+ i++;
28553
+ }
28554
+ out.push({ kind: 'table', head, align, rows });
28555
+ continue;
28556
+ }
28557
+ // Lists.
28558
+ const item = UL.exec(line) ?? OL.exec(line);
28559
+ if (item && depth < STRCT_MD_LIMITS.depth) {
28560
+ flushParagraph();
28561
+ const ordered = !UL.test(line);
28562
+ const start = ordered ? Number(OL.exec(line)[2]) : 1;
28563
+ const items = [];
28564
+ const baseIndent = item[1].length;
28565
+ while (i < lines.length) {
28566
+ const m = UL.exec(lines[i]) ?? OL.exec(lines[i]);
28567
+ const sameKind = m && !UL.test(lines[i]) === ordered;
28568
+ if (!m || !sameKind || m[1].length < baseIndent)
28569
+ break;
28570
+ if (m[1].length > baseIndent)
28571
+ break; // a deeper list is the item's own
28572
+ const buffer = [m[3]];
28573
+ i++;
28574
+ // Continuation lines: anything indented past the marker, and blanks
28575
+ // between them, belong to this item.
28576
+ while (i < lines.length) {
28577
+ const next = lines[i];
28578
+ if (!next.trim()) {
28579
+ const after = lines[i + 1] ?? '';
28580
+ if (after.trim() && /^\s{2,}/.test(after)) {
28581
+ buffer.push('');
28582
+ i++;
28583
+ continue;
28584
+ }
28585
+ break;
28586
+ }
28587
+ if (/^\s{2,}/.test(next) && !UL.test(next.slice(baseIndent)) === false) {
28588
+ buffer.push(next.slice(Math.min(baseIndent + 2, next.length - next.trimStart().length)));
28589
+ i++;
28590
+ continue;
28591
+ }
28592
+ if (/^\s{2,}/.test(next)) {
28593
+ buffer.push(next.trimStart());
28594
+ i++;
28595
+ continue;
28596
+ }
28597
+ break;
28598
+ }
28599
+ items.push(parseBlocks(buffer, depth + 1, options));
28600
+ }
28601
+ out.push({ kind: 'list', ordered, start, items });
28602
+ continue;
28603
+ }
28604
+ paragraph.push(line);
28605
+ i++;
28606
+ }
28607
+ flushParagraph();
28608
+ return out;
28609
+ }
28610
+ function splitRow(line) {
28611
+ const trimmed = line.trim().replace(/^\|/, '').replace(/\|$/, '');
28612
+ const cells = [];
28613
+ let cur = '';
28614
+ for (let i = 0; i < trimmed.length; i++) {
28615
+ const ch = trimmed[i];
28616
+ if (ch === '\\' && trimmed[i + 1] === '|') {
28617
+ cur += '|';
28618
+ i++;
28619
+ continue;
28620
+ }
28621
+ if (ch === '|') {
28622
+ cells.push(cur.trim());
28623
+ cur = '';
28624
+ continue;
28625
+ }
28626
+ cur += ch;
28627
+ }
28628
+ cells.push(cur.trim());
28629
+ return cells;
28630
+ }
28631
+ function toAlign(cell) {
28632
+ const c = cell.trim();
28633
+ const left = c.startsWith(':');
28634
+ const right = c.endsWith(':');
28635
+ if (left && right)
28636
+ return 'center';
28637
+ if (right)
28638
+ return 'end';
28639
+ if (left)
28640
+ return 'start';
28641
+ return null;
28642
+ }
28643
+ /**
28644
+ * Inline reading. Code spans win over everything (their content is literal),
28645
+ * then links, then the emphasis delimiters — each bounded.
28646
+ */
28647
+ function parseInlines(source, options = {}) {
28648
+ const out = [];
28649
+ let buf = '';
28650
+ let i = 0;
28651
+ let budget = STRCT_MD_LIMITS.delimiters;
28652
+ const flush = () => {
28653
+ if (!buf)
28654
+ return;
28655
+ out.push(...applyTokens(buf, options.tokenize));
28656
+ buf = '';
28657
+ };
28658
+ while (i < source.length) {
28659
+ const ch = source[i];
28660
+ // A backslash escapes the next punctuation character.
28661
+ if (ch === '\\' && i + 1 < source.length && /[\\`*_{}[\]()#+\-.!|~>]/.test(source[i + 1])) {
28662
+ buf += source[i + 1];
28663
+ i += 2;
28664
+ continue;
28665
+ }
28666
+ // Code span: the longest run of backticks closes it.
28667
+ if (ch === '`') {
28668
+ const run = /^`+/.exec(source.slice(i))[0];
28669
+ const close = source.indexOf(run, i + run.length);
28670
+ if (close !== -1) {
28671
+ flush();
28672
+ out.push({ kind: 'code', text: source.slice(i + run.length, close).trim() });
28673
+ i = close + run.length;
28674
+ continue;
28675
+ }
28676
+ }
28677
+ // Hard break: two spaces at the end of a line, or a backslash.
28678
+ if (ch === '\n') {
28679
+ const hard = buf.endsWith(' ') || buf.endsWith('\\');
28680
+ buf = buf.replace(/( {2,}|\\)$/, '');
28681
+ flush();
28682
+ out.push(hard ? { kind: 'break' } : { kind: 'text', text: ' ' });
28683
+ i++;
28684
+ continue;
28685
+ }
28686
+ // Autolink.
28687
+ if (ch === '<') {
28688
+ const auto = /^<((?:https?:\/\/|mailto:)[^ >]+)>/.exec(source.slice(i));
28689
+ if (auto) {
28690
+ flush();
28691
+ out.push({ kind: 'link', href: auto[1], children: [{ kind: 'text', text: auto[1] }] });
28692
+ i += auto[0].length;
28693
+ continue;
28694
+ }
28695
+ }
28696
+ // Link. An image is read as a link's text — nothing is ever loaded.
28697
+ if ((ch === '[' || (ch === '!' && source[i + 1] === '[')) && budget-- > 0) {
28698
+ const at = ch === '!' ? i + 1 : i;
28699
+ const close = matchBracket(source, at);
28700
+ if (close !== -1 && source[close + 1] === '(') {
28701
+ const end = source.indexOf(')', close + 2);
28702
+ if (end !== -1) {
28703
+ const label = source.slice(at + 1, close);
28704
+ const href = source
28705
+ .slice(close + 2, end)
28706
+ .trim()
28707
+ .split(/\s+/)[0];
28708
+ flush();
28709
+ if (ch === '!') {
28710
+ // An image's alt text is all a reader gets: no request is made.
28711
+ out.push({ kind: 'text', text: label });
28712
+ }
28713
+ else {
28714
+ out.push({ kind: 'link', href, children: parseInlines(label, options) });
28715
+ }
28716
+ i = end + 1;
28717
+ continue;
28718
+ }
28719
+ }
28720
+ }
28721
+ // Emphasis, strong, strikethrough. A delimiter only opens when something
28722
+ // follows it immediately and only closes when something precedes it —
28723
+ // the flanking rule, which is what keeps "2 * 3 * 4" arithmetic.
28724
+ if ((ch === '*' || ch === '_' || ch === '~') && budget-- > 0) {
28725
+ const run = new RegExp(`^\\${ch}+`).exec(source.slice(i))[0];
28726
+ const len = ch === '~' ? (run.length >= 2 ? 2 : 0) : Math.min(run.length, 2);
28727
+ const opens = !/^\s|^$/.test(source.slice(i + run.length, i + run.length + 1) || '');
28728
+ if (len && opens) {
28729
+ const marker = ch.repeat(len);
28730
+ const close = findCloser(source, marker, i + len);
28731
+ if (close !== -1 && close > i + len) {
28732
+ const inner = source.slice(i + len, close);
28733
+ flush();
28734
+ const children = parseInlines(inner, options);
28735
+ out.push(ch === '~'
28736
+ ? { kind: 'del', children }
28737
+ : len === 2
28738
+ ? { kind: 'strong', children }
28739
+ : { kind: 'em', children });
28740
+ i = close + len;
28741
+ continue;
28742
+ }
28743
+ }
28744
+ }
28745
+ buf += ch;
28746
+ i++;
28747
+ }
28748
+ flush();
28749
+ return out;
28750
+ }
28751
+ /**
28752
+ * The next closing delimiter: one with a non-space before it. An opener with
28753
+ * no such closer is not a delimiter at all, it is the character written.
28754
+ */
28755
+ function findCloser(source, marker, from) {
28756
+ let at = source.indexOf(marker, from);
28757
+ while (at !== -1) {
28758
+ const before = source[at - 1];
28759
+ if (before && !/\s/.test(before))
28760
+ return at;
28761
+ at = source.indexOf(marker, at + marker.length);
28762
+ }
28763
+ return -1;
28764
+ }
28765
+ /** The index of the `]` matching the `[` at `from`, or -1. */
28766
+ function matchBracket(source, from) {
28767
+ let depth = 0;
28768
+ for (let i = from; i < source.length; i++) {
28769
+ if (source[i] === '\\') {
28770
+ i++;
28771
+ continue;
28772
+ }
28773
+ if (source[i] === '[')
28774
+ depth++;
28775
+ else if (source[i] === ']') {
28776
+ depth--;
28777
+ if (!depth)
28778
+ return i;
28779
+ }
28780
+ }
28781
+ return -1;
28782
+ }
28783
+ /** Hand a run of plain text to the consumer's tokenizer, if it gave one. */
28784
+ function applyTokens(text, tokenize) {
28785
+ if (!tokenize)
28786
+ return [{ kind: 'text', text }];
28787
+ let found;
28788
+ try {
28789
+ found = tokenize(text) ?? [];
28790
+ }
28791
+ catch {
28792
+ return [{ kind: 'text', text }];
28793
+ }
28794
+ const valid = found
28795
+ .filter((t) => t && t.length > 0 && t.start >= 0 && t.start + t.length <= text.length)
28796
+ .sort((a, b) => a.start - b.start);
28797
+ const out = [];
28798
+ let at = 0;
28799
+ for (const t of valid) {
28800
+ if (t.start < at)
28801
+ continue; // overlapping claims are dropped
28802
+ if (t.start > at)
28803
+ out.push({ kind: 'text', text: text.slice(at, t.start) });
28804
+ out.push({
28805
+ kind: 'token',
28806
+ token: t.token,
28807
+ text: text.slice(t.start, t.start + t.length),
28808
+ data: t.data,
28809
+ });
28810
+ at = t.start + t.length;
28811
+ }
28812
+ if (at < text.length)
28813
+ out.push({ kind: 'text', text: text.slice(at) });
28814
+ return out.length ? out : [{ kind: 'text', text }];
28815
+ }
28816
+ /**
28817
+ * What a link may do. Anything that is not plainly safe stays the text the
28818
+ * model wrote: `javascript:`, `data:`, a protocol-relative `//host`, or
28819
+ * anything this does not recognise.
28820
+ */
28821
+ function strctClassifyHref(href) {
28822
+ const raw = (href ?? '').trim();
28823
+ if (!raw)
28824
+ return null;
28825
+ // Control characters and whitespace inside a URL are a smuggling trick.
28826
+ if (/[\u0000-\u001f\u007f\s]/.test(raw))
28827
+ return null;
28828
+ if (/^https?:\/\/[^/]/i.test(raw))
28829
+ return 'external';
28830
+ if (/^mailto:[^@\s]+@[^@\s]+$/i.test(raw))
28831
+ return 'external';
28832
+ if (raw.startsWith('//'))
28833
+ return null;
28834
+ if (raw.startsWith('/'))
28835
+ return 'internal';
28836
+ return null;
28837
+ }
28838
+
28839
+ /**
28840
+ * A template for a span the consumer claimed through `tokens`, keyed by the
28841
+ * token's kind:
28842
+ *
28843
+ * <ng-template strctMarkdownToken="object" let-text let-data="data">
28844
+ * <strct-tag [href]="data.url">{{ text }}</strct-tag>
28845
+ * </ng-template>
28846
+ */
28847
+ class StrctMarkdownToken {
28848
+ /** Which token kind this template draws. */
28849
+ strctMarkdownToken = input('', /* @ts-ignore */
28850
+ ...(ngDevMode ? [{ debugName: "strctMarkdownToken" }] : /* istanbul ignore next */ []));
28851
+ template = inject(TemplateRef);
28852
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMarkdownToken, deps: [], target: i0.ɵɵFactoryTarget.Directive });
28853
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: StrctMarkdownToken, isStandalone: true, selector: "ng-template[strctMarkdownToken]", inputs: { strctMarkdownToken: { classPropertyName: "strctMarkdownToken", publicName: "strctMarkdownToken", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
28854
+ }
28855
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMarkdownToken, decorators: [{
28856
+ type: Directive,
28857
+ args: [{ selector: 'ng-template[strctMarkdownToken]' }]
28858
+ }], propDecorators: { strctMarkdownToken: [{ type: i0.Input, args: [{ isSignal: true, alias: "strctMarkdownToken", required: false }] }] } });
28859
+ /**
28860
+ * Markdown as a tree of components, never as HTML.
28861
+ *
28862
+ * <strct-markdown [source]="reply()" [streaming]="busy()" />
28863
+ *
28864
+ * A model's reply is Markdown: drawn as plain text it leaves tables as rows of
28865
+ * pipes and `**` everywhere, and an app that reaches for a Markdown library
28866
+ * ends up handing the browser HTML — `innerHTML` plus a sanitiser — which a
28867
+ * sealed, reviewed build cannot accept.
28868
+ *
28869
+ * Every word reaches the page as a text node. Raw HTML is shown as the
28870
+ * characters it is. Images are never loaded: an image's alt text is all a
28871
+ * reader gets. A link opens in a new tab only when it is plainly http(s);
28872
+ * an in-app path is handed to `(navigate)`; anything else — `javascript:`,
28873
+ * `data:`, `//host` — stays the text the model wrote.
28874
+ */
28875
+ class StrctMarkdown {
28876
+ /** The Markdown to read. */
28877
+ source = input('', /* @ts-ignore */
28878
+ ...(ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []));
28879
+ /**
28880
+ * A tree read elsewhere, instead of `source` — how the component draws the
28881
+ * blocks inside a list item or a quote without re-reading them.
28882
+ */
28883
+ tree = input(null, /* @ts-ignore */
28884
+ ...(ngDevMode ? [{ debugName: "tree" }] : /* istanbul ignore next */ []));
28885
+ /**
28886
+ * The reply is still arriving: an unterminated fence is already a code
28887
+ * block, so streamed code does not flash as a paragraph first, and the
28888
+ * caret sits at the end of the last line.
28889
+ */
28890
+ streaming = input(false, { ...(ngDevMode ? { debugName: "streaming" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
28891
+ /**
28892
+ * How far the model's headings are pulled down. A message is a region
28893
+ * inside a dialog, so `#` should not be the page's `h1`: the default of 2
28894
+ * makes it an `h3`.
28895
+ */
28896
+ headingOffset = input(2, /* @ts-ignore */
28897
+ ...(ngDevMode ? [{ debugName: "headingOffset" }] : /* istanbul ignore next */ []));
28898
+ /**
28899
+ * What a link may do, when the default rule is not what the app wants.
28900
+ * Returning `null` leaves the link as text.
28901
+ */
28902
+ linkPolicy = input(null, /* @ts-ignore */
28903
+ ...(ngDevMode ? [{ debugName: "linkPolicy" }] : /* istanbul ignore next */ []));
28904
+ /** Claim runs of plain text — an inventory id becomes an object chip. */
28905
+ tokens = input(null, /* @ts-ignore */
28906
+ ...(ngDevMode ? [{ debugName: "tokens" }] : /* istanbul ignore next */ []));
28907
+ /** An in-app link was followed; the consumer routes it. */
28908
+ navigate = output();
28909
+ tokenDefs = contentChildren(StrctMarkdownToken, /* @ts-ignore */
28910
+ ...(ngDevMode ? [{ debugName: "tokenDefs" }] : /* istanbul ignore next */ []));
28911
+ blocks = computed(() => {
28912
+ const given = this.tree();
28913
+ if (given)
28914
+ return given;
28915
+ return strctParseMarkdown(this.source(), {
28916
+ streaming: this.streaming(),
28917
+ tokenize: this.tokens() ?? undefined,
28918
+ });
28919
+ }, /* @ts-ignore */
28920
+ ...(ngDevMode ? [{ debugName: "blocks" }] : /* istanbul ignore next */ []));
28921
+ level(level) {
28922
+ return Math.min(6, Math.max(1, level + this.headingOffset()));
28923
+ }
28924
+ hrefKind(href) {
28925
+ const own = this.linkPolicy();
28926
+ return own ? own(href) : strctClassifyHref(href);
28927
+ }
28928
+ tokenTemplate(kind) {
28929
+ return this.tokenDefs().find((d) => d.strctMarkdownToken() === kind)?.template ?? null;
28930
+ }
28931
+ /** The caret belongs to the last block, and only while streaming. */
28932
+ caretOn(index) {
28933
+ return this.streaming() && !this.tree() && index === this.blocks().length - 1;
28934
+ }
28935
+ onInternal(event, href) {
28936
+ // A modified click is the browser's — a new tab, a saved link.
28937
+ const e = event;
28938
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0)
28939
+ return;
28940
+ event.preventDefault();
28941
+ this.navigate.emit(href);
28942
+ }
28943
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMarkdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
28944
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctMarkdown, isStandalone: true, selector: "strct-markdown", inputs: { source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, tree: { classPropertyName: "tree", publicName: "tree", isSignal: true, isRequired: false, transformFunction: null }, streaming: { classPropertyName: "streaming", publicName: "streaming", isSignal: true, isRequired: false, transformFunction: null }, headingOffset: { classPropertyName: "headingOffset", publicName: "headingOffset", isSignal: true, isRequired: false, transformFunction: null }, linkPolicy: { classPropertyName: "linkPolicy", publicName: "linkPolicy", isSignal: true, isRequired: false, transformFunction: null }, tokens: { classPropertyName: "tokens", publicName: "tokens", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate" }, host: { classAttribute: "strct-md" }, queries: [{ propertyName: "tokenDefs", predicate: StrctMarkdownToken, isSignal: true }], ngImport: i0, template: `
28945
+ @for (block of blocks(); track $index) {
28946
+ @switch (block.kind) {
28947
+ @case ('p') {
28948
+ <p class="strct-md__p">
28949
+ <ng-container
28950
+ [ngTemplateOutlet]="inlineRun"
28951
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
28952
+ />
28953
+ @if (caretOn($index)) {
28954
+ <span class="strct-md__caret" aria-hidden="true"></span>
28955
+ }
28956
+ </p>
28957
+ }
28958
+ @case ('heading') {
28959
+ <!-- A message is a region inside a dialog, so the document's own
28960
+ outline is not the model's to set: the offset pulls them down. -->
28961
+ @switch (level(block.level)) {
28962
+ @case (1) {
28963
+ <h1 class="strct-md__h">
28964
+ <ng-container
28965
+ [ngTemplateOutlet]="inlineRun"
28966
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
28967
+ />
28968
+ </h1>
28969
+ }
28970
+ @case (2) {
28971
+ <h2 class="strct-md__h">
28972
+ <ng-container
28973
+ [ngTemplateOutlet]="inlineRun"
28974
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
28975
+ />
28976
+ </h2>
28977
+ }
28978
+ @case (3) {
28979
+ <h3 class="strct-md__h">
28980
+ <ng-container
28981
+ [ngTemplateOutlet]="inlineRun"
28982
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
28983
+ />
28984
+ </h3>
28985
+ }
28986
+ @case (4) {
28987
+ <h4 class="strct-md__h">
28988
+ <ng-container
28989
+ [ngTemplateOutlet]="inlineRun"
28990
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
28991
+ />
28992
+ </h4>
28993
+ }
28994
+ @case (5) {
28995
+ <h5 class="strct-md__h">
28996
+ <ng-container
28997
+ [ngTemplateOutlet]="inlineRun"
28998
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
28999
+ />
29000
+ </h5>
29001
+ }
29002
+ @default {
29003
+ <h6 class="strct-md__h">
29004
+ <ng-container
29005
+ [ngTemplateOutlet]="inlineRun"
29006
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
29007
+ />
29008
+ </h6>
29009
+ }
29010
+ }
29011
+ }
29012
+ @case ('code') {
29013
+ <strct-code class="strct-md__code" [code]="block.text" [language]="block.lang" wrap />
29014
+ }
29015
+ @case ('rule') {
29016
+ <hr class="strct-md__rule" />
29017
+ }
29018
+ @case ('quote') {
29019
+ <blockquote class="strct-md__quote">
29020
+ <ng-container
29021
+ [ngTemplateOutlet]="blockRun"
29022
+ [ngTemplateOutletContext]="{ $implicit: block.blocks }"
29023
+ />
29024
+ </blockquote>
29025
+ }
29026
+ @case ('list') {
29027
+ @if (block.ordered) {
29028
+ <ol class="strct-md__list" [attr.start]="block.start">
29029
+ @for (item of block.items; track $index) {
29030
+ <li>
29031
+ <ng-container
29032
+ [ngTemplateOutlet]="blockRun"
29033
+ [ngTemplateOutletContext]="{ $implicit: item }"
29034
+ />
29035
+ </li>
29036
+ }
29037
+ </ol>
29038
+ } @else {
29039
+ <ul class="strct-md__list">
29040
+ @for (item of block.items; track $index) {
29041
+ <li>
29042
+ <ng-container
29043
+ [ngTemplateOutlet]="blockRun"
29044
+ [ngTemplateOutletContext]="{ $implicit: item }"
29045
+ />
29046
+ </li>
29047
+ }
29048
+ </ul>
29049
+ }
29050
+ }
29051
+ @case ('table') {
29052
+ <div class="strct-md__tablewrap">
29053
+ <table class="strct-md__table">
29054
+ <thead>
29055
+ <tr>
29056
+ @for (cell of block.head; track $index) {
29057
+ <th scope="col" [style.text-align]="block.align[$index] ?? 'start'">
29058
+ <ng-container
29059
+ [ngTemplateOutlet]="inlineRun"
29060
+ [ngTemplateOutletContext]="{ $implicit: cell }"
29061
+ />
29062
+ </th>
29063
+ }
29064
+ </tr>
29065
+ </thead>
29066
+ <tbody>
29067
+ @for (row of block.rows; track $index) {
29068
+ <tr>
29069
+ @for (cell of row; track $index) {
29070
+ <td [style.text-align]="block.align[$index] ?? 'start'">
29071
+ <ng-container
29072
+ [ngTemplateOutlet]="inlineRun"
29073
+ [ngTemplateOutletContext]="{ $implicit: cell }"
29074
+ />
29075
+ </td>
29076
+ }
29077
+ </tr>
29078
+ }
29079
+ </tbody>
29080
+ </table>
29081
+ </div>
29082
+ }
29083
+ }
29084
+ }
29085
+
29086
+ <!-- Blocks inside a list item or a quote. -->
29087
+ <ng-template #blockRun let-blocks>
29088
+ @for (b of blocks; track $index) {
29089
+ <strct-markdown
29090
+ [tree]="[b]"
29091
+ [headingOffset]="headingOffset()"
29092
+ [linkPolicy]="linkPolicy()"
29093
+ (navigate)="navigate.emit($event)"
29094
+ />
29095
+ }
29096
+ </ng-template>
29097
+
29098
+ <!-- One run of inline nodes. -->
29099
+ <ng-template #inlineRun let-inlines>
29100
+ @for (node of inlines; track $index) {
29101
+ @switch (node.kind) {
29102
+ @case ('text') {
29103
+ <ng-container>{{ node.text }}</ng-container>
29104
+ }
29105
+ @case ('code') {
29106
+ <code class="strct-md__codespan">{{ node.text }}</code>
29107
+ }
29108
+ @case ('break') {
29109
+ <br />
29110
+ }
29111
+ @case ('strong') {
29112
+ <strong>
29113
+ <ng-container
29114
+ [ngTemplateOutlet]="inlineRun"
29115
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29116
+ />
29117
+ </strong>
29118
+ }
29119
+ @case ('em') {
29120
+ <em>
29121
+ <ng-container
29122
+ [ngTemplateOutlet]="inlineRun"
29123
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29124
+ />
29125
+ </em>
29126
+ }
29127
+ @case ('del') {
29128
+ <del>
29129
+ <ng-container
29130
+ [ngTemplateOutlet]="inlineRun"
29131
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29132
+ />
29133
+ </del>
29134
+ }
29135
+ @case ('token') {
29136
+ @if (tokenTemplate(node.token); as tpl) {
29137
+ <ng-container
29138
+ [ngTemplateOutlet]="tpl"
29139
+ [ngTemplateOutletContext]="{ $implicit: node.text, data: node.data }"
29140
+ />
29141
+ } @else {
29142
+ <ng-container>{{ node.text }}</ng-container>
29143
+ }
29144
+ }
29145
+ @case ('link') {
29146
+ @switch (hrefKind(node.href)) {
29147
+ @case ('external') {
29148
+ <a
29149
+ class="strct-md__link"
29150
+ [attr.href]="node.href"
29151
+ target="_blank"
29152
+ rel="noopener noreferrer"
29153
+ >
29154
+ <ng-container
29155
+ [ngTemplateOutlet]="inlineRun"
29156
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29157
+ />
29158
+ <strct-icon
29159
+ class="strct-md__ext"
29160
+ strictName="externalLink"
29161
+ [size]="11"
29162
+ aria-hidden="true"
29163
+ />
29164
+ </a>
29165
+ }
29166
+ @case ('internal') {
29167
+ <a
29168
+ class="strct-md__link"
29169
+ [attr.href]="node.href"
29170
+ (click)="onInternal($event, node.href)"
29171
+ >
29172
+ <ng-container
29173
+ [ngTemplateOutlet]="inlineRun"
29174
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29175
+ />
29176
+ </a>
29177
+ }
29178
+ @default {
29179
+ <!-- Not a link we will follow: the characters, as written. -->
29180
+ <ng-container
29181
+ [ngTemplateOutlet]="inlineRun"
29182
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29183
+ />
29184
+ }
29185
+ }
29186
+ }
29187
+ }
29188
+ }
29189
+ </ng-template>
29190
+ `, isInline: true, styles: [".strct-md{display:block;color:var(--t1);font-size:13px;line-height:1.55}.strct-md__p{margin:0 0 var(--space-2)}.strct-md__p:last-child{margin-bottom:0}.strct-md__h{margin:var(--space-3) 0 var(--space-2);font-size:14px;font-weight:600;color:var(--t1)}.strct-md__h:first-child{margin-top:0}.strct-md__list{margin:0 0 var(--space-2);padding-inline-start:20px}.strct-md__list li{margin-block:2px}.strct-md__quote{margin:0 0 var(--space-2);padding-inline-start:var(--space-3);border-inline-start:2px solid var(--b2);color:var(--t2)}.strct-md__rule{border:0;border-block-start:1px solid var(--b1);margin:var(--space-3) 0}.strct-md__codespan{font-family:var(--mono);font-size:.92em;background:var(--bg-2);border-radius:var(--radius-sm);padding:1px 4px}.strct-md__code{margin-block-end:var(--space-2)}.strct-md__link{color:var(--acc)}.strct-md__ext{margin-inline-start:2px;vertical-align:-1px}.strct-md__tablewrap{overflow-x:auto;margin-block-end:var(--space-2)}.strct-md__table{border-collapse:collapse;width:max-content;min-width:100%}.strct-md__table th,.strct-md__table td{border:1px solid var(--b1);padding:4px 8px;text-align:start}.strct-md__table th{background:var(--bg-2);font-weight:600}.strct-md__caret{display:inline-block;width:2px;height:1em;margin-inline-start:2px;vertical-align:-2px;background:var(--acc);animation:strct-md-caret 1s steps(2) infinite}@keyframes strct-md-caret{50%{opacity:0}}@media(prefers-reduced-motion:reduce){.strct-md__caret{animation:none}}\n"], dependencies: [{ kind: "component", type: StrctMarkdown, selector: "strct-markdown", inputs: ["source", "tree", "streaming", "headingOffset", "linkPolicy", "tokens"], outputs: ["navigate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StrctCode, selector: "strct-code", inputs: ["code", "title", "language", "copyable", "collapsible", "collapsed", "lineNumbers", "wrap", "maxHeight", "collapseLabel", "expandLabel", "regionLabel"], outputs: ["collapsedChange"] }, { 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 });
29191
+ }
29192
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMarkdown, decorators: [{
29193
+ type: Component,
29194
+ args: [{ selector: 'strct-markdown', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, StrctCode, StrctIcon], template: `
29195
+ @for (block of blocks(); track $index) {
29196
+ @switch (block.kind) {
29197
+ @case ('p') {
29198
+ <p class="strct-md__p">
29199
+ <ng-container
29200
+ [ngTemplateOutlet]="inlineRun"
29201
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
29202
+ />
29203
+ @if (caretOn($index)) {
29204
+ <span class="strct-md__caret" aria-hidden="true"></span>
29205
+ }
29206
+ </p>
29207
+ }
29208
+ @case ('heading') {
29209
+ <!-- A message is a region inside a dialog, so the document's own
29210
+ outline is not the model's to set: the offset pulls them down. -->
29211
+ @switch (level(block.level)) {
29212
+ @case (1) {
29213
+ <h1 class="strct-md__h">
29214
+ <ng-container
29215
+ [ngTemplateOutlet]="inlineRun"
29216
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
29217
+ />
29218
+ </h1>
29219
+ }
29220
+ @case (2) {
29221
+ <h2 class="strct-md__h">
29222
+ <ng-container
29223
+ [ngTemplateOutlet]="inlineRun"
29224
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
29225
+ />
29226
+ </h2>
29227
+ }
29228
+ @case (3) {
29229
+ <h3 class="strct-md__h">
29230
+ <ng-container
29231
+ [ngTemplateOutlet]="inlineRun"
29232
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
29233
+ />
29234
+ </h3>
29235
+ }
29236
+ @case (4) {
29237
+ <h4 class="strct-md__h">
29238
+ <ng-container
29239
+ [ngTemplateOutlet]="inlineRun"
29240
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
29241
+ />
29242
+ </h4>
29243
+ }
29244
+ @case (5) {
29245
+ <h5 class="strct-md__h">
29246
+ <ng-container
29247
+ [ngTemplateOutlet]="inlineRun"
29248
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
29249
+ />
29250
+ </h5>
29251
+ }
29252
+ @default {
29253
+ <h6 class="strct-md__h">
29254
+ <ng-container
29255
+ [ngTemplateOutlet]="inlineRun"
29256
+ [ngTemplateOutletContext]="{ $implicit: block.inlines }"
29257
+ />
29258
+ </h6>
29259
+ }
29260
+ }
29261
+ }
29262
+ @case ('code') {
29263
+ <strct-code class="strct-md__code" [code]="block.text" [language]="block.lang" wrap />
29264
+ }
29265
+ @case ('rule') {
29266
+ <hr class="strct-md__rule" />
29267
+ }
29268
+ @case ('quote') {
29269
+ <blockquote class="strct-md__quote">
29270
+ <ng-container
29271
+ [ngTemplateOutlet]="blockRun"
29272
+ [ngTemplateOutletContext]="{ $implicit: block.blocks }"
29273
+ />
29274
+ </blockquote>
29275
+ }
29276
+ @case ('list') {
29277
+ @if (block.ordered) {
29278
+ <ol class="strct-md__list" [attr.start]="block.start">
29279
+ @for (item of block.items; track $index) {
29280
+ <li>
29281
+ <ng-container
29282
+ [ngTemplateOutlet]="blockRun"
29283
+ [ngTemplateOutletContext]="{ $implicit: item }"
29284
+ />
29285
+ </li>
29286
+ }
29287
+ </ol>
29288
+ } @else {
29289
+ <ul class="strct-md__list">
29290
+ @for (item of block.items; track $index) {
29291
+ <li>
29292
+ <ng-container
29293
+ [ngTemplateOutlet]="blockRun"
29294
+ [ngTemplateOutletContext]="{ $implicit: item }"
29295
+ />
29296
+ </li>
29297
+ }
29298
+ </ul>
29299
+ }
29300
+ }
29301
+ @case ('table') {
29302
+ <div class="strct-md__tablewrap">
29303
+ <table class="strct-md__table">
29304
+ <thead>
29305
+ <tr>
29306
+ @for (cell of block.head; track $index) {
29307
+ <th scope="col" [style.text-align]="block.align[$index] ?? 'start'">
29308
+ <ng-container
29309
+ [ngTemplateOutlet]="inlineRun"
29310
+ [ngTemplateOutletContext]="{ $implicit: cell }"
29311
+ />
29312
+ </th>
29313
+ }
29314
+ </tr>
29315
+ </thead>
29316
+ <tbody>
29317
+ @for (row of block.rows; track $index) {
29318
+ <tr>
29319
+ @for (cell of row; track $index) {
29320
+ <td [style.text-align]="block.align[$index] ?? 'start'">
29321
+ <ng-container
29322
+ [ngTemplateOutlet]="inlineRun"
29323
+ [ngTemplateOutletContext]="{ $implicit: cell }"
29324
+ />
29325
+ </td>
29326
+ }
29327
+ </tr>
29328
+ }
29329
+ </tbody>
29330
+ </table>
29331
+ </div>
29332
+ }
29333
+ }
29334
+ }
29335
+
29336
+ <!-- Blocks inside a list item or a quote. -->
29337
+ <ng-template #blockRun let-blocks>
29338
+ @for (b of blocks; track $index) {
29339
+ <strct-markdown
29340
+ [tree]="[b]"
29341
+ [headingOffset]="headingOffset()"
29342
+ [linkPolicy]="linkPolicy()"
29343
+ (navigate)="navigate.emit($event)"
29344
+ />
29345
+ }
29346
+ </ng-template>
29347
+
29348
+ <!-- One run of inline nodes. -->
29349
+ <ng-template #inlineRun let-inlines>
29350
+ @for (node of inlines; track $index) {
29351
+ @switch (node.kind) {
29352
+ @case ('text') {
29353
+ <ng-container>{{ node.text }}</ng-container>
29354
+ }
29355
+ @case ('code') {
29356
+ <code class="strct-md__codespan">{{ node.text }}</code>
29357
+ }
29358
+ @case ('break') {
29359
+ <br />
29360
+ }
29361
+ @case ('strong') {
29362
+ <strong>
29363
+ <ng-container
29364
+ [ngTemplateOutlet]="inlineRun"
29365
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29366
+ />
29367
+ </strong>
29368
+ }
29369
+ @case ('em') {
29370
+ <em>
29371
+ <ng-container
29372
+ [ngTemplateOutlet]="inlineRun"
29373
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29374
+ />
29375
+ </em>
29376
+ }
29377
+ @case ('del') {
29378
+ <del>
29379
+ <ng-container
29380
+ [ngTemplateOutlet]="inlineRun"
29381
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29382
+ />
29383
+ </del>
29384
+ }
29385
+ @case ('token') {
29386
+ @if (tokenTemplate(node.token); as tpl) {
29387
+ <ng-container
29388
+ [ngTemplateOutlet]="tpl"
29389
+ [ngTemplateOutletContext]="{ $implicit: node.text, data: node.data }"
29390
+ />
29391
+ } @else {
29392
+ <ng-container>{{ node.text }}</ng-container>
29393
+ }
29394
+ }
29395
+ @case ('link') {
29396
+ @switch (hrefKind(node.href)) {
29397
+ @case ('external') {
29398
+ <a
29399
+ class="strct-md__link"
29400
+ [attr.href]="node.href"
29401
+ target="_blank"
29402
+ rel="noopener noreferrer"
29403
+ >
29404
+ <ng-container
29405
+ [ngTemplateOutlet]="inlineRun"
29406
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29407
+ />
29408
+ <strct-icon
29409
+ class="strct-md__ext"
29410
+ strictName="externalLink"
29411
+ [size]="11"
29412
+ aria-hidden="true"
29413
+ />
29414
+ </a>
29415
+ }
29416
+ @case ('internal') {
29417
+ <a
29418
+ class="strct-md__link"
29419
+ [attr.href]="node.href"
29420
+ (click)="onInternal($event, node.href)"
29421
+ >
29422
+ <ng-container
29423
+ [ngTemplateOutlet]="inlineRun"
29424
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29425
+ />
29426
+ </a>
29427
+ }
29428
+ @default {
29429
+ <!-- Not a link we will follow: the characters, as written. -->
29430
+ <ng-container
29431
+ [ngTemplateOutlet]="inlineRun"
29432
+ [ngTemplateOutletContext]="{ $implicit: node.children }"
29433
+ />
29434
+ }
29435
+ }
29436
+ }
29437
+ }
29438
+ }
29439
+ </ng-template>
29440
+ `, host: { class: 'strct-md' }, styles: [".strct-md{display:block;color:var(--t1);font-size:13px;line-height:1.55}.strct-md__p{margin:0 0 var(--space-2)}.strct-md__p:last-child{margin-bottom:0}.strct-md__h{margin:var(--space-3) 0 var(--space-2);font-size:14px;font-weight:600;color:var(--t1)}.strct-md__h:first-child{margin-top:0}.strct-md__list{margin:0 0 var(--space-2);padding-inline-start:20px}.strct-md__list li{margin-block:2px}.strct-md__quote{margin:0 0 var(--space-2);padding-inline-start:var(--space-3);border-inline-start:2px solid var(--b2);color:var(--t2)}.strct-md__rule{border:0;border-block-start:1px solid var(--b1);margin:var(--space-3) 0}.strct-md__codespan{font-family:var(--mono);font-size:.92em;background:var(--bg-2);border-radius:var(--radius-sm);padding:1px 4px}.strct-md__code{margin-block-end:var(--space-2)}.strct-md__link{color:var(--acc)}.strct-md__ext{margin-inline-start:2px;vertical-align:-1px}.strct-md__tablewrap{overflow-x:auto;margin-block-end:var(--space-2)}.strct-md__table{border-collapse:collapse;width:max-content;min-width:100%}.strct-md__table th,.strct-md__table td{border:1px solid var(--b1);padding:4px 8px;text-align:start}.strct-md__table th{background:var(--bg-2);font-weight:600}.strct-md__caret{display:inline-block;width:2px;height:1em;margin-inline-start:2px;vertical-align:-2px;background:var(--acc);animation:strct-md-caret 1s steps(2) infinite}@keyframes strct-md-caret{50%{opacity:0}}@media(prefers-reduced-motion:reduce){.strct-md__caret{animation:none}}\n"] }]
29441
+ }], propDecorators: { source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], tree: [{ type: i0.Input, args: [{ isSignal: true, alias: "tree", required: false }] }], streaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "streaming", required: false }] }], headingOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "headingOffset", required: false }] }], linkPolicy: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkPolicy", required: false }] }], tokens: [{ type: i0.Input, args: [{ isSignal: true, alias: "tokens", required: false }] }], navigate: [{ type: i0.Output, args: ["navigate"] }], tokenDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctMarkdownToken), { isSignal: true }] }] } });
29442
+
28297
29443
  /**
28298
29444
  * Public API surface of the strct UI library.
28299
29445
  */
@@ -28303,5 +29449,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
28303
29449
  * Generated bundle index. Do not edit.
28304
29450
  */
28305
29451
 
28306
- export { FOCUSABLE_SELECTOR, STRCT_DP_DOW, STRCT_DP_DOW_FULL, STRCT_DP_MONTHS, STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_NOTIFICATION_HISTORY_LIMIT, STRCT_PALETTES, STRCT_RAW_ICONS, STRCT_TIME_RANGE_PRESETS, STRCT_WIZARD_DEFAULTS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAlertActions, StrctAnnouncer, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctBytesPipe, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCardHeaderActions, StrctCardHeaderLeading, StrctCardHeaderMeta, StrctCardHeaderNote, StrctCascadeColumn, StrctCascadeHost, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChange, StrctChart, StrctChatAttachment, StrctChatComposer, StrctChatMessage, StrctChatThread, StrctCheckbox, StrctChips, StrctCode, StrctCodeInline, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctConfirmOutlet, StrctConfirmService, StrctContextMenu, StrctContextMenuTrigger, StrctControlDescription, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatagridActionBarCaption, StrctDatepicker, StrctDatetimePicker, StrctDesc, StrctDescriptionList, StrctDetailPaneActions, StrctDetailPaneMeta, StrctDetailPaneTitle, StrctDiff, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctDropdownItemAction, StrctDropdownTrigger, StrctDurationPipe, StrctEmptyState, StrctField, StrctFieldGroup, StrctFieldHint, StrctFieldPrefix, StrctFieldSuffix, StrctFieldValue, StrctFile, StrctFilterBar, StrctFlow, StrctFlowNodeTemplate, StrctFooter, StrctGauge, StrctGroupHeaderDef, StrctHeader, StrctHeatmap, StrctHero, StrctHotkeysHelp, StrctHotkeysService, StrctIcon, StrctInlineEdit, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctLegend, StrctList, StrctListItem, StrctListItemDescription, StrctListItemLeading, StrctListItemMeta, StrctListItemTrailing, StrctLiveIndicator, StrctLogViewer, StrctLogin, StrctMediaFrame, StrctMenuPanel, StrctMenuService, StrctMenubar, StrctMetricTile, StrctModal, StrctModalContent, StrctNav, StrctNavItem, StrctNotificationCenter, StrctNumber, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPageHeaderTitleMeta, StrctPagination, StrctPassword, StrctPopover, StrctPopoverTrigger, StrctProgress, StrctQr, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRatePipe, StrctRating, StrctReorder, StrctReorderGroup, StrctReorderHandle, StrctReorderItem, StrctResizeHandle, StrctRowDetailDef, StrctSearchbox, StrctSectionHeader, StrctSectionHeaderActions, StrctSectionHeaderMeta, StrctSectionMenu, StrctSegmented, StrctSelect, StrctShell, StrctShellService, StrctSiPipe, StrctSignpost, StrctSignpostTrigger, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctSplitButton, StrctSplitter, StrctStack, StrctStackItem, StrctStatusDot, StrctStep, StrctStepAction, StrctSteps, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctTagLeading, StrctThemeService, StrctThemeSwitcher, StrctTimeRangePicker, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctToolbar, StrctToolbarCaption, StrctToolbarSpacer, StrctTooltip, StrctTour, StrctTransfer, StrctTree, StrctTreeNode, StrctTreeSelect, StrctVerticalNav, StrctWatermark, StrctWindow, StrctWindowDock, StrctWindowDockItem, StrctWindowService, StrctWizard, StrctWizardAside, __qrInternals, focusFirstIn, keepTabInside, lockBodyScroll, parseAnsi, provideStrctWizardDefaults, registerStrctIcon, restoreFocus, saveFocusedElement, strctCheckHostDisplay, strctCheckHostInputs, strctComputeDiff, strctDpPad, strctFormatBytes, strctFormatDuration, strctFormatRate, strctFormatSi, strctQrMatrix, strctValidationIcon, strctValidationTone, unlockBodyScroll };
29452
+ export { FOCUSABLE_SELECTOR, STRCT_DP_DOW, STRCT_DP_DOW_FULL, STRCT_DP_MONTHS, STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_MD_LIMITS, STRCT_NOTIFICATION_HISTORY_LIMIT, STRCT_PALETTES, STRCT_RAW_ICONS, STRCT_TIME_RANGE_PRESETS, STRCT_WIZARD_DEFAULTS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAlertActions, StrctAnnouncer, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctBytesPipe, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCardHeaderActions, StrctCardHeaderLeading, StrctCardHeaderMeta, StrctCardHeaderNote, StrctCascadeColumn, StrctCascadeHost, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChange, StrctChart, StrctChatAttachment, StrctChatComposer, StrctChatMessage, StrctChatMessageActions, StrctChatThread, StrctCheckbox, StrctChips, StrctCode, StrctCodeInline, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctConfirmOutlet, StrctConfirmService, StrctContextMenu, StrctContextMenuTrigger, StrctControlDescription, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatagridActionBarCaption, StrctDatepicker, StrctDatetimePicker, StrctDesc, StrctDescriptionList, StrctDetailPaneActions, StrctDetailPaneMeta, StrctDetailPaneTitle, StrctDiff, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctDropdownItemAction, StrctDropdownTrigger, StrctDurationPipe, StrctEmptyState, StrctField, StrctFieldGroup, StrctFieldHint, StrctFieldPrefix, StrctFieldSuffix, StrctFieldValue, StrctFile, StrctFilterBar, StrctFlow, StrctFlowNodeTemplate, StrctFooter, StrctGauge, StrctGroupHeaderDef, StrctHeader, StrctHeatmap, StrctHero, StrctHotkeysHelp, StrctHotkeysService, StrctIcon, StrctInlineEdit, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctLegend, StrctList, StrctListItem, StrctListItemDescription, StrctListItemLeading, StrctListItemMeta, StrctListItemTrailing, StrctLiveIndicator, StrctLogViewer, StrctLogin, StrctMarkdown, StrctMarkdownToken, StrctMediaFrame, StrctMenuPanel, StrctMenuService, StrctMenubar, StrctMetricTile, StrctModal, StrctModalContent, StrctNav, StrctNavItem, StrctNotificationCenter, StrctNumber, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPageHeaderTitleMeta, StrctPagination, StrctPassword, StrctPopover, StrctPopoverTrigger, StrctProgress, StrctQr, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRatePipe, StrctRating, StrctReorder, StrctReorderGroup, StrctReorderHandle, StrctReorderItem, StrctResizeHandle, StrctRowDetailDef, StrctSearchbox, StrctSectionHeader, StrctSectionHeaderActions, StrctSectionHeaderMeta, StrctSectionMenu, StrctSegmented, StrctSelect, StrctShell, StrctShellService, StrctSiPipe, StrctSignpost, StrctSignpostTrigger, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctSplitButton, StrctSplitter, StrctStack, StrctStackItem, StrctStatusDot, StrctStep, StrctStepAction, StrctSteps, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctTagLeading, StrctThemeService, StrctThemeSwitcher, StrctTimeRangePicker, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctToolbar, StrctToolbarCaption, StrctToolbarSpacer, StrctTooltip, StrctTour, StrctTransfer, StrctTree, StrctTreeNode, StrctTreeSelect, StrctVerticalNav, StrctWatermark, StrctWindow, StrctWindowDock, StrctWindowDockItem, StrctWindowService, StrctWizard, StrctWizardAside, __qrInternals, focusFirstIn, keepTabInside, lockBodyScroll, parseAnsi, parseInlines, provideStrctWizardDefaults, registerStrctIcon, restoreFocus, saveFocusedElement, strctCheckHostDisplay, strctCheckHostInputs, strctClassifyHref, strctComputeDiff, strctDpPad, strctFormatBytes, strctFormatDuration, strctFormatRate, strctFormatSi, strctParseMarkdown, strctQrMatrix, strctValidationIcon, strctValidationTone, unlockBodyScroll };
28307
29453
  //# sourceMappingURL=akcelik-strct.mjs.map