@sentropic/design-system-angular 0.36.59 → 0.36.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,39 +1,233 @@
1
- import { Component, Input as NgInput } from "@angular/core";
1
+ import { Component, ElementRef, EventEmitter, Input as NgInput, Output, ViewChild, } from "@angular/core";
2
2
  import { classNames } from "./classNames.js";
3
3
  import { Menu } from "./Menu.js";
4
4
  import * as i0 from "@angular/core";
5
+ // Anchor spacing between panel and trigger.
6
+ const GAP = 4;
7
+ // Anti-overflow margin between the panel and the viewport edge.
8
+ const VIEWPORT_MARGIN = 8;
9
+ // Height floor: below this threshold we keep a usable scrollable window rather
10
+ // than a crushed menu (rare case of a trigger glued to the edge).
11
+ const MIN_HEIGHT = 160;
12
+ /**
13
+ * Pure position solver — mirrors Svelte's `computePosition()` / React's
14
+ * `computePosition` callback so it can be unit-tested without a real
15
+ * `window`/DOM (this package's tests run in a Node environment, no jsdom).
16
+ */
17
+ export function computeMenuPopoverPosition(rect, placement, align, viewport) {
18
+ const verticalUp = placement === "top-start" || placement === "top-end";
19
+ const horizontalEnd = placement === "bottom-end" || placement === "top-end";
20
+ const resolvedAlign = align ?? (horizontalEnd ? "end" : "start");
21
+ const alignEnd = resolvedAlign === "end";
22
+ const alignCenter = resolvedAlign === "center";
23
+ let top;
24
+ let maxHeight;
25
+ if (verticalUp) {
26
+ // Panel bottom edge sits above the trigger (transform handles the flip);
27
+ // available space is what's ABOVE the trigger.
28
+ top = rect.top + viewport.scrollY - GAP;
29
+ maxHeight = Math.max(rect.top - GAP - VIEWPORT_MARGIN, MIN_HEIGHT);
30
+ }
31
+ else {
32
+ top = rect.bottom + viewport.scrollY + GAP;
33
+ // Space actually available BELOW the trigger — `max-height: 100vh` alone
34
+ // ignores the panel's offset and overflows past the bottom edge.
35
+ maxHeight = Math.max(viewport.height - rect.bottom - GAP - VIEWPORT_MARGIN, MIN_HEIGHT);
36
+ }
37
+ let left;
38
+ if (resolvedAlign === "end") {
39
+ left = rect.right + viewport.scrollX;
40
+ }
41
+ else if (resolvedAlign === "center") {
42
+ left = rect.left + viewport.scrollX + rect.width / 2;
43
+ }
44
+ else {
45
+ left = rect.left + viewport.scrollX;
46
+ }
47
+ return { top, left, maxHeight, alignEnd, alignCenter };
48
+ }
49
+ export function menuPopoverIsWithin(event, node) {
50
+ if (!node)
51
+ return false;
52
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
53
+ if (path.includes(node))
54
+ return true;
55
+ const target = event.target;
56
+ return Boolean(target && node.contains(target));
57
+ }
5
58
  export class MenuPopover {
6
59
  static stComponentName = "MenuPopover";
7
60
  componentName = "MenuPopover";
61
+ /** Convenience (not in the Svelte contract): renders a <st-menu> from `items` instead of projected content. */
8
62
  items;
9
- open;
10
- placement;
63
+ open = false;
64
+ /** Anchor element used for position computation and outside-click exclusion (Svelte-canonical). */
65
+ trigger = null;
66
+ placement = "bottom-start";
67
+ align;
68
+ /** Accessible name for the panel (Svelte-canonical: applied as aria-label on role="dialog"). */
69
+ label;
11
70
  classInput;
71
+ closeOnOutside = true;
72
+ closeOnEscape = true;
73
+ openChange = new EventEmitter();
74
+ panel;
75
+ top = 0;
76
+ left = 0;
77
+ maxHeight = 0;
78
+ alignEnd = false;
79
+ alignCenter = false;
80
+ listenersActive = false;
81
+ onScroll = () => this.computePosition();
82
+ onResize = () => this.computePosition();
83
+ onPointerDown = (event) => {
84
+ if (!this.open || !this.closeOnOutside)
85
+ return;
86
+ if (menuPopoverIsWithin(event, this.panel?.nativeElement))
87
+ return;
88
+ if (menuPopoverIsWithin(event, this.trigger))
89
+ return;
90
+ this.close();
91
+ };
92
+ onKeyDown = (event) => {
93
+ if (!this.open || !this.closeOnEscape)
94
+ return;
95
+ if (event.key === "Escape") {
96
+ event.preventDefault();
97
+ this.close();
98
+ }
99
+ };
12
100
  get hostClass() {
13
- return ["st-menuPopover", this.classInput].filter(Boolean).join(" ");
101
+ return classNames("st-menuPopover", `st-menuPopover--${this.placement}`, this.alignEnd ? "st-menuPopover--alignEnd" : null, this.alignCenter ? "st-menuPopover--alignCenter" : null, this.classInput);
102
+ }
103
+ get panelStyle() {
104
+ return `top: ${this.top}px; left: ${this.left}px;${this.maxHeight ? ` max-height: ${this.maxHeight}px;` : ""}`;
105
+ }
106
+ ngAfterViewInit() {
107
+ this.registerWindowListeners();
108
+ if (this.open)
109
+ this.computePosition();
110
+ }
111
+ ngOnChanges(changes) {
112
+ if (typeof window === "undefined")
113
+ return;
114
+ if (!this.open)
115
+ return;
116
+ if (changes["open"] || changes["trigger"] || changes["placement"] || changes["align"]) {
117
+ // Recompute after Angular renders the panel for the new state.
118
+ queueMicrotask(() => {
119
+ if (this.open)
120
+ this.computePosition();
121
+ });
122
+ }
123
+ }
124
+ ngOnDestroy() {
125
+ this.unregisterWindowListeners();
126
+ }
127
+ computePosition() {
128
+ if (!this.trigger)
129
+ return;
130
+ if (typeof window === "undefined")
131
+ return;
132
+ const rect = this.trigger.getBoundingClientRect();
133
+ const pos = computeMenuPopoverPosition({ top: rect.top, bottom: rect.bottom, left: rect.left, right: rect.right, width: rect.width }, this.placement, this.align, { height: window.innerHeight, scrollX: window.scrollX, scrollY: window.scrollY });
134
+ this.top = pos.top;
135
+ this.left = pos.left;
136
+ this.maxHeight = pos.maxHeight;
137
+ this.alignEnd = pos.alignEnd;
138
+ this.alignCenter = pos.alignCenter;
139
+ }
140
+ close() {
141
+ if (!this.open)
142
+ return;
143
+ this.open = false;
144
+ this.openChange.emit(false);
145
+ }
146
+ registerWindowListeners() {
147
+ if (this.listenersActive)
148
+ return;
149
+ if (typeof window === "undefined")
150
+ return;
151
+ this.listenersActive = true;
152
+ window.addEventListener("scroll", this.onScroll, true);
153
+ window.addEventListener("resize", this.onResize);
154
+ window.addEventListener("pointerdown", this.onPointerDown);
155
+ window.addEventListener("keydown", this.onKeyDown);
156
+ }
157
+ unregisterWindowListeners() {
158
+ if (!this.listenersActive)
159
+ return;
160
+ this.listenersActive = false;
161
+ if (typeof window === "undefined")
162
+ return;
163
+ window.removeEventListener("scroll", this.onScroll, true);
164
+ window.removeEventListener("resize", this.onResize);
165
+ window.removeEventListener("pointerdown", this.onPointerDown);
166
+ window.removeEventListener("keydown", this.onKeyDown);
14
167
  }
15
168
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MenuPopover, deps: [], target: i0.ɵɵFactoryTarget.Component });
16
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: MenuPopover, isStandalone: true, selector: "st-menu-popover", inputs: { items: "items", open: "open", placement: "placement", classInput: ["class", "classInput"] }, ngImport: i0, template: `
17
- <div [attr.data-st-component]="componentName" [class]="hostClass">
18
- <ng-content></ng-content>
19
- </div>
20
- `, isInline: true, styles: [":host { display: contents; }"] });
169
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MenuPopover, isStandalone: true, selector: "st-menu-popover", inputs: { items: "items", open: "open", trigger: "trigger", placement: "placement", align: "align", label: "label", classInput: ["class", "classInput"], closeOnOutside: "closeOnOutside", closeOnEscape: "closeOnEscape" }, outputs: { openChange: "openChange" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
170
+ @if (open) {
171
+ <div
172
+ #panel
173
+ [attr.data-st-component]="componentName"
174
+ [class]="hostClass"
175
+ role="dialog"
176
+ [attr.aria-label]="label"
177
+ [style]="panelStyle"
178
+ >
179
+ @if (items && items.length) {
180
+ <st-menu [items]="items" role="presentation"></st-menu>
181
+ } @else {
182
+ <ng-content></ng-content>
183
+ }
184
+ </div>
185
+ }
186
+ `, isInline: true, styles: [":host { display: contents; }"], dependencies: [{ kind: "component", type: Menu, selector: "st-menu", inputs: ["items", "dense", "role", "class"], outputs: ["select"] }] });
21
187
  }
22
188
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MenuPopover, decorators: [{
23
189
  type: Component,
24
- args: [{ selector: "st-menu-popover", standalone: true, template: `
25
- <div [attr.data-st-component]="componentName" [class]="hostClass">
26
- <ng-content></ng-content>
27
- </div>
190
+ args: [{ selector: "st-menu-popover", standalone: true, imports: [Menu], template: `
191
+ @if (open) {
192
+ <div
193
+ #panel
194
+ [attr.data-st-component]="componentName"
195
+ [class]="hostClass"
196
+ role="dialog"
197
+ [attr.aria-label]="label"
198
+ [style]="panelStyle"
199
+ >
200
+ @if (items && items.length) {
201
+ <st-menu [items]="items" role="presentation"></st-menu>
202
+ } @else {
203
+ <ng-content></ng-content>
204
+ }
205
+ </div>
206
+ }
28
207
  `, styles: [":host { display: contents; }"] }]
29
208
  }], propDecorators: { items: [{
30
209
  type: NgInput
31
210
  }], open: [{
32
211
  type: NgInput
212
+ }], trigger: [{
213
+ type: NgInput
33
214
  }], placement: [{
34
215
  type: NgInput
216
+ }], align: [{
217
+ type: NgInput
218
+ }], label: [{
219
+ type: NgInput
35
220
  }], classInput: [{
36
221
  type: NgInput,
37
222
  args: ["class"]
223
+ }], closeOnOutside: [{
224
+ type: NgInput
225
+ }], closeOnEscape: [{
226
+ type: NgInput
227
+ }], openChange: [{
228
+ type: Output
229
+ }], panel: [{
230
+ type: ViewChild,
231
+ args: ["panel"]
38
232
  }] } });
39
233
  //# sourceMappingURL=MenuPopover.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MenuPopover.js","sourceRoot":"","sources":["../src/MenuPopover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,eAAe,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;;AAuBjC,MAAM,OAAO,WAAW;IACtB,MAAM,CAAU,eAAe,GAAG,aAAa,CAAC;IACvC,aAAa,GAAG,aAAa,CAAC;IAC5B,KAAK,CAAc;IACnB,IAAI,CAAW;IACf,SAAS,CAAwB;IAC1B,UAAU,CAAU;IAEtC,IAAI,SAAS;QACX,OAAO,CAAC,gBAAgB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvE,CAAC;wGAVU,WAAW;4FAAX,WAAW,kLANZ;;;;GAIT;;4FAEU,WAAW;kBAVvB,SAAS;+BACE,iBAAiB,cACf,IAAI,YAEN;;;;GAIT;;sBAKA,OAAO;;sBACP,OAAO;;sBACP,OAAO;;sBACP,OAAO;uBAAC,OAAO"}
1
+ {"version":3,"file":"MenuPopover.js","sourceRoot":"","sources":["../src/MenuPopover.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import { ElementRef, EventEmitter } from "@angular/core";
2
+ import * as i0 from "@angular/core";
3
+ export type PanelSectionProps = {
4
+ id: string;
5
+ label: string;
6
+ /**
7
+ * Convenience (not in the Svelte contract): renders the header-trailing
8
+ * actions slot wrapper. Angular content projection has no way to detect
9
+ * whether optional projected content was actually supplied the way a
10
+ * Svelte snippet prop (`actions?: Snippet`) can — same reasoning as
11
+ * `SelectableRow`'s `leading`/`trailing`/`caption` flags. Project the
12
+ * actions markup with `slot="actions"` and set this to `true`.
13
+ */
14
+ actions?: boolean;
15
+ class?: string;
16
+ };
17
+ /**
18
+ * One section of a `PanelStack`. Faithful Angular port of
19
+ * `PanelSection.svelte` — see `PanelStack.ts` for the ratified contract and
20
+ * for why Angular-specific coordination departs from Svelte's context.
21
+ *
22
+ * `isPrimary`/`expanded`/`scrollOwner` are plain `@Input()`s here (not
23
+ * derived from an injected context, unlike Svelte's `getContext`): a
24
+ * `PanelStack` ancestor discovers this component via `@ContentChildren` and
25
+ * pushes these three directly onto each instance it finds. A `PanelSection`
26
+ * with no such ancestor (never synced) simply keeps its declared defaults —
27
+ * which mirror `PanelSection.svelte`'s own "no context" fallback exactly:
28
+ * fully expanded, scroll-owning, non-primary, so it still renders sanely in
29
+ * isolation.
30
+ */
31
+ export declare class PanelSection {
32
+ private readonly host;
33
+ static readonly stComponentName = "PanelSection";
34
+ readonly componentName = "PanelSection";
35
+ id: string;
36
+ label: string;
37
+ actions?: boolean;
38
+ classInput?: string;
39
+ /** Angular-idiom @Input()s (not literal Svelte props) — see the class-level
40
+ * doc comment: a PanelStack ancestor pushes these directly. Defaults match
41
+ * PanelSection.svelte's own no-context fallback. */
42
+ isPrimary: boolean;
43
+ expanded: boolean;
44
+ scrollOwner: boolean;
45
+ /** Angular-idiom addition: fired with this section's `id` when its header
46
+ * trigger is activated. `PanelStack` subscribes to every discovered
47
+ * section's emitter to drive `toggle()`. */
48
+ readonly toggleRequested: EventEmitter<string>;
49
+ private headerElRef?;
50
+ constructor(host: ElementRef<HTMLElement>);
51
+ /** The section's own root element — is the host itself (see the
52
+ * class-level doc comment) — read by `PanelStack` for ResizeObserver
53
+ * measurement. */
54
+ get rootElement(): HTMLElement;
55
+ /** Split-primary's auto-collapse measures whichever section is CURRENTLY
56
+ * primary by its header height specifically — see `PanelStack.ts`'s
57
+ * `register` doc; sticky-item ignores it. `undefined` until the view has
58
+ * rendered (e.g. a bare, un-mounted instance in a test). */
59
+ get headerElement(): HTMLElement | undefined;
60
+ get triggerId(): string;
61
+ get regionId(): string;
62
+ get hostClass(): string;
63
+ get bodyClasses(): string;
64
+ get iconClass(): string;
65
+ onTriggerClick(): void;
66
+ static ɵfac: i0.ɵɵFactoryDeclaration<PanelSection, never>;
67
+ static ɵcmp: i0.ɵɵComponentDeclaration<PanelSection, "st-panel-section", never, { "id": { "alias": "id"; "required": false; }; "label": { "alias": "label"; "required": false; }; "actions": { "alias": "actions"; "required": false; }; "classInput": { "alias": "class"; "required": false; }; "isPrimary": { "alias": "isPrimary"; "required": false; }; "expanded": { "alias": "expanded"; "required": false; }; "scrollOwner": { "alias": "scrollOwner"; "required": false; }; }, { "toggleRequested": "toggleRequested"; }, never, ["[slot='actions']", "*"], true, never>;
68
+ }
69
+ //# sourceMappingURL=PanelSection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PanelSection.d.ts","sourceRoot":"","sources":["../src/PanelSection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAE,YAAY,EAAuC,MAAM,eAAe,CAAC;;AAKzG,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAsDa,YAAY;IAuBX,OAAO,CAAC,QAAQ,CAAC,IAAI;IAtBjC,MAAM,CAAC,QAAQ,CAAC,eAAe,kBAAkB;IACjD,QAAQ,CAAC,aAAa,kBAAkB;IAE7B,EAAE,EAAG,MAAM,CAAC;IACZ,KAAK,EAAG,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IAEtC;;yDAEqD;IAC1C,SAAS,UAAS;IAClB,QAAQ,UAAQ;IAChB,WAAW,UAAQ;IAE9B;;iDAE6C;IACnC,QAAQ,CAAC,eAAe,uBAA8B;IAEzC,OAAO,CAAC,WAAW,CAAC,CAA0B;gBAExC,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC;IAE1D;;uBAEmB;IACnB,IAAI,WAAW,IAAI,WAAW,CAE7B;IAED;;;iEAG6D;IAC7D,IAAI,aAAa,IAAI,WAAW,GAAG,SAAS,CAE3C;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,WAAW,IAAI,MAAM,CASxB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,cAAc,IAAI,IAAI;yCAnEX,YAAY;2CAAZ,YAAY;CAsExB"}
@@ -0,0 +1,191 @@
1
+ import { Component, ElementRef, EventEmitter, Input as NgInput, Output, ViewChild } from "@angular/core";
2
+ import { classNames } from "./classNames.js";
3
+ import { Icon } from "./Icon.js";
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * One section of a `PanelStack`. Faithful Angular port of
7
+ * `PanelSection.svelte` — see `PanelStack.ts` for the ratified contract and
8
+ * for why Angular-specific coordination departs from Svelte's context.
9
+ *
10
+ * `isPrimary`/`expanded`/`scrollOwner` are plain `@Input()`s here (not
11
+ * derived from an injected context, unlike Svelte's `getContext`): a
12
+ * `PanelStack` ancestor discovers this component via `@ContentChildren` and
13
+ * pushes these three directly onto each instance it finds. A `PanelSection`
14
+ * with no such ancestor (never synced) simply keeps its declared defaults —
15
+ * which mirror `PanelSection.svelte`'s own "no context" fallback exactly:
16
+ * fully expanded, scroll-owning, non-primary, so it still renders sanely in
17
+ * isolation.
18
+ */
19
+ export class PanelSection {
20
+ host;
21
+ static stComponentName = "PanelSection";
22
+ componentName = "PanelSection";
23
+ id;
24
+ label;
25
+ actions;
26
+ classInput;
27
+ /** Angular-idiom @Input()s (not literal Svelte props) — see the class-level
28
+ * doc comment: a PanelStack ancestor pushes these directly. Defaults match
29
+ * PanelSection.svelte's own no-context fallback. */
30
+ isPrimary = false;
31
+ expanded = true;
32
+ scrollOwner = true;
33
+ /** Angular-idiom addition: fired with this section's `id` when its header
34
+ * trigger is activated. `PanelStack` subscribes to every discovered
35
+ * section's emitter to drive `toggle()`. */
36
+ toggleRequested = new EventEmitter();
37
+ headerElRef;
38
+ constructor(host) {
39
+ this.host = host;
40
+ }
41
+ /** The section's own root element — is the host itself (see the
42
+ * class-level doc comment) — read by `PanelStack` for ResizeObserver
43
+ * measurement. */
44
+ get rootElement() {
45
+ return this.host.nativeElement;
46
+ }
47
+ /** Split-primary's auto-collapse measures whichever section is CURRENTLY
48
+ * primary by its header height specifically — see `PanelStack.ts`'s
49
+ * `register` doc; sticky-item ignores it. `undefined` until the view has
50
+ * rendered (e.g. a bare, un-mounted instance in a test). */
51
+ get headerElement() {
52
+ return this.headerElRef?.nativeElement;
53
+ }
54
+ get triggerId() {
55
+ return `st-panelSection-trigger-${this.id}`;
56
+ }
57
+ get regionId() {
58
+ return `st-panelSection-region-${this.id}`;
59
+ }
60
+ get hostClass() {
61
+ return classNames("st-panelSection", this.scrollOwner ? "st-panelSection--scrollOwner" : null, this.classInput);
62
+ }
63
+ get bodyClasses() {
64
+ return classNames("st-panelSection__body", this.scrollOwner
65
+ ? "st-panelSection__body--scrollOwner"
66
+ : !this.expanded
67
+ ? "st-panelSection__body--collapsed"
68
+ : null);
69
+ }
70
+ get iconClass() {
71
+ return classNames("st-panelSection__icon", this.expanded ? "st-panelSection__icon--expanded" : null);
72
+ }
73
+ onTriggerClick() {
74
+ this.toggleRequested.emit(this.id);
75
+ }
76
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PanelSection, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
77
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: PanelSection, isStandalone: true, selector: "st-panel-section", inputs: { id: "id", label: "label", actions: "actions", classInput: ["class", "classInput"], isPrimary: "isPrimary", expanded: "expanded", scrollOwner: "scrollOwner" }, outputs: { toggleRequested: "toggleRequested" }, host: { properties: { "attr.data-st-component": "componentName", "attr.data-panel-section-id": "id", "class": "hostClass" } }, viewQueries: [{ propertyName: "headerElRef", first: true, predicate: ["headerEl"], descendants: true }], ngImport: i0, template: `
78
+ <div class="st-panelSection__header" #headerEl>
79
+ <h3 class="st-panelSection__heading">
80
+ @if (isPrimary) {
81
+ <span class="st-panelSection__title st-panelSection__title--primary" [id]="triggerId">{{ label }}</span>
82
+ } @else {
83
+ <button
84
+ type="button"
85
+ class="st-panelSection__trigger"
86
+ [attr.aria-expanded]="expanded ? 'true' : 'false'"
87
+ [attr.aria-controls]="regionId"
88
+ [id]="triggerId"
89
+ (click)="onTriggerClick()"
90
+ >
91
+ <span class="st-panelSection__title">{{ label }}</span>
92
+ <span [class]="iconClass" aria-hidden="true">
93
+ <st-icon name="chevron-down" [size]="18"></st-icon>
94
+ </span>
95
+ </button>
96
+ }
97
+ </h3>
98
+ @if (actions) {
99
+ <div class="st-panelSection__actions">
100
+ <ng-content select="[slot='actions']"></ng-content>
101
+ </div>
102
+ }
103
+ </div>
104
+ <!-- Mounted once, always — collapsing is purely a class/CSS concern
105
+ (sizing + hiding this region), never a conditional block. A chat
106
+ session, a map or any other stateful widget rendered here survives
107
+ collapse/expand without remounting; see AppShell's
108
+ panelCollapse="accordion" region for the same technique. -->
109
+ <div [class]="bodyClasses" role="region" [id]="regionId" [attr.aria-labelledby]="triggerId">
110
+ <ng-content></ng-content>
111
+ </div>
112
+ `, isInline: true, dependencies: [{ kind: "component", type: Icon, selector: "st-icon", inputs: ["name", "size", "strokeWidth", "title", "class"] }] });
113
+ }
114
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PanelSection, decorators: [{
115
+ type: Component,
116
+ args: [{
117
+ selector: "st-panel-section",
118
+ standalone: true,
119
+ imports: [Icon],
120
+ // The host tag itself carries `.st-panelSection` (no inner wrapper div) —
121
+ // see `PanelStack.ts`'s class-level doc comment for why: the shared
122
+ // stylesheet's `.st-panelStack > .st-panelSection + .st-panelSection`
123
+ // separator rule needs `.st-panelSection` to be a genuine DOM child of
124
+ // `.st-panelStack`, and `display:flex`/`flex:0 0 auto`/`flex:1 1 0` (from
125
+ // that same class) need to apply to a box that actually participates as a
126
+ // flex item in the stack's layout — both of which an extra
127
+ // `:host{display:contents}`-wrapped inner div would undermine.
128
+ host: {
129
+ "[attr.data-st-component]": "componentName",
130
+ "[attr.data-panel-section-id]": "id",
131
+ "[class]": "hostClass",
132
+ },
133
+ template: `
134
+ <div class="st-panelSection__header" #headerEl>
135
+ <h3 class="st-panelSection__heading">
136
+ @if (isPrimary) {
137
+ <span class="st-panelSection__title st-panelSection__title--primary" [id]="triggerId">{{ label }}</span>
138
+ } @else {
139
+ <button
140
+ type="button"
141
+ class="st-panelSection__trigger"
142
+ [attr.aria-expanded]="expanded ? 'true' : 'false'"
143
+ [attr.aria-controls]="regionId"
144
+ [id]="triggerId"
145
+ (click)="onTriggerClick()"
146
+ >
147
+ <span class="st-panelSection__title">{{ label }}</span>
148
+ <span [class]="iconClass" aria-hidden="true">
149
+ <st-icon name="chevron-down" [size]="18"></st-icon>
150
+ </span>
151
+ </button>
152
+ }
153
+ </h3>
154
+ @if (actions) {
155
+ <div class="st-panelSection__actions">
156
+ <ng-content select="[slot='actions']"></ng-content>
157
+ </div>
158
+ }
159
+ </div>
160
+ <!-- Mounted once, always — collapsing is purely a class/CSS concern
161
+ (sizing + hiding this region), never a conditional block. A chat
162
+ session, a map or any other stateful widget rendered here survives
163
+ collapse/expand without remounting; see AppShell's
164
+ panelCollapse="accordion" region for the same technique. -->
165
+ <div [class]="bodyClasses" role="region" [id]="regionId" [attr.aria-labelledby]="triggerId">
166
+ <ng-content></ng-content>
167
+ </div>
168
+ `,
169
+ }]
170
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { id: [{
171
+ type: NgInput
172
+ }], label: [{
173
+ type: NgInput
174
+ }], actions: [{
175
+ type: NgInput
176
+ }], classInput: [{
177
+ type: NgInput,
178
+ args: ["class"]
179
+ }], isPrimary: [{
180
+ type: NgInput
181
+ }], expanded: [{
182
+ type: NgInput
183
+ }], scrollOwner: [{
184
+ type: NgInput
185
+ }], toggleRequested: [{
186
+ type: Output
187
+ }], headerElRef: [{
188
+ type: ViewChild,
189
+ args: ["headerEl"]
190
+ }] } });
191
+ //# sourceMappingURL=PanelSection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PanelSection.js","sourceRoot":"","sources":["../src/PanelSection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEzG,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;;AAiBjC;;;;;;;;;;;;;GAaG;AAuDH,MAAM,OAAO,YAAY;IAuBM;IAtB7B,MAAM,CAAU,eAAe,GAAG,cAAc,CAAC;IACxC,aAAa,GAAG,cAAc,CAAC;IAE7B,EAAE,CAAU;IACZ,KAAK,CAAU;IACf,OAAO,CAAW;IACX,UAAU,CAAU;IAEtC;;yDAEqD;IAC1C,SAAS,GAAG,KAAK,CAAC;IAClB,QAAQ,GAAG,IAAI,CAAC;IAChB,WAAW,GAAG,IAAI,CAAC;IAE9B;;iDAE6C;IAC1B,eAAe,GAAG,IAAI,YAAY,EAAU,CAAC;IAEjC,WAAW,CAA2B;IAErE,YAA6B,IAA6B;QAA7B,SAAI,GAAJ,IAAI,CAAyB;IAAG,CAAC;IAE9D;;uBAEmB;IACnB,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC;IACjC,CAAC;IAED;;;iEAG6D;IAC7D,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC;IACzC,CAAC;IAED,IAAI,SAAS;QACX,OAAO,2BAA2B,IAAI,CAAC,EAAE,EAAE,CAAC;IAC9C,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,0BAA0B,IAAI,CAAC,EAAE,EAAE,CAAC;IAC7C,CAAC;IAED,IAAI,SAAS;QACX,OAAO,UAAU,CAAC,iBAAiB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAClH,CAAC;IAED,IAAI,WAAW;QACb,OAAO,UAAU,CACf,uBAAuB,EACvB,IAAI,CAAC,WAAW;YACd,CAAC,CAAC,oCAAoC;YACtC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ;gBACd,CAAC,CAAC,kCAAkC;gBACpC,CAAC,CAAC,IAAI,CACX,CAAC;IACJ,CAAC;IAED,IAAI,SAAS;QACX,OAAO,UAAU,CAAC,uBAAuB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACvG,CAAC;IAED,cAAc;QACZ,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC;wGArEU,YAAY;4FAAZ,YAAY,8gBArCb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCT,4DAjDS,IAAI;;4FAmDH,YAAY;kBAtDxB,SAAS;mBAAC;oBACT,QAAQ,EAAE,kBAAkB;oBAC5B,UAAU,EAAE,IAAI;oBAChB,OAAO,EAAE,CAAC,IAAI,CAAC;oBACf,0EAA0E;oBAC1E,oEAAoE;oBACpE,sEAAsE;oBACtE,uEAAuE;oBACvE,0EAA0E;oBAC1E,0EAA0E;oBAC1E,2DAA2D;oBAC3D,+DAA+D;oBAC/D,IAAI,EAAE;wBACJ,0BAA0B,EAAE,eAAe;wBAC3C,8BAA8B,EAAE,IAAI;wBACpC,SAAS,EAAE,WAAW;qBACvB;oBACD,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCT;iBACF;;sBAKE,OAAO;;sBACP,OAAO;;sBACP,OAAO;;sBACP,OAAO;uBAAC,OAAO;;sBAKf,OAAO;;sBACP,OAAO;;sBACP,OAAO;;sBAKP,MAAM;;sBAEN,SAAS;uBAAC,UAAU"}