@compsych-ui-components/angular 5.1.8 → 6.0.8
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.
- package/esm2022/index.js.map +1 -1
- package/esm2022/lib/accordion/accordion-types.js.map +1 -1
- package/esm2022/lib/accordion/accordion.js +116 -98
- package/esm2022/lib/accordion/accordion.js.map +1 -1
- package/esm2022/lib/button/button.js +6 -0
- package/esm2022/lib/button/button.js.map +1 -1
- package/esm2022/lib/header-nav/header-nav.js +50 -20
- package/esm2022/lib/header-nav/header-nav.js.map +1 -1
- package/esm2022/lib/sidebar/sidebar.js +17 -5
- package/esm2022/lib/sidebar/sidebar.js.map +1 -1
- package/lib/accordion/accordion-types.d.ts +21 -0
- package/lib/accordion/accordion.d.ts +13 -0
- package/lib/button/button.d.ts +7 -0
- package/lib/header-nav/header-nav.d.ts +48 -21
- package/lib/sidebar/sidebar.d.ts +22 -5
- package/package.json +1 -1
package/esm2022/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/angular/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAOzD,MAAM,OAAO,qBAAqB;wGAArB,qBAAqB;yGAArB,qBAAqB,YAHtB,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,aACzE,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB;yGAExE,qBAAqB;;4FAArB,qBAAqB;kBAJjC,QAAQ;mBAAC;oBACR,OAAO,EAAE,CAAC,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,CAAC;oBACpF,OAAO,EAAE,CAAC,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,CAAC;iBACrF","sourcesContent":["import { NgModule } from '@angular/core';\nimport { ButtonComponent } from './lib/button/button';\nimport { AccordionComponent } from './lib/accordion/accordion';\nimport { HeaderNavComponent } from './lib/header-nav/header-nav';\nimport { SidebarComponent } from './lib/sidebar/sidebar';\n\nexport { ButtonComponent } from './lib/button/button';\nexport type { ButtonVariant } from './lib/button/button';\nexport { AccordionComponent } from './lib/accordion/accordion';\nexport type { AccordionItem, AccordionButton, AccordionVariant, NestedAccordionItem } from './lib/accordion/accordion-types';\nexport { HeaderNavComponent } from './lib/header-nav/header-nav';\nexport type { HeaderNavLevel, NavAction } from './lib/header-nav/header-nav';\nexport { SidebarComponent } from './lib/sidebar/sidebar';\nexport type { SidebarItem } from './lib/sidebar/sidebar';\n\n@NgModule({\n imports: [ButtonComponent, AccordionComponent, HeaderNavComponent, SidebarComponent],\n exports: [ButtonComponent, AccordionComponent, HeaderNavComponent, SidebarComponent],\n})\nexport class CompsychAngularModule {}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/angular/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAOzD,MAAM,OAAO,qBAAqB;wGAArB,qBAAqB;yGAArB,qBAAqB,YAHtB,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,aACzE,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB;yGAExE,qBAAqB;;4FAArB,qBAAqB;kBAJjC,QAAQ;mBAAC;oBACR,OAAO,EAAE,CAAC,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,CAAC;oBACpF,OAAO,EAAE,CAAC,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,CAAC;iBACrF","sourcesContent":["import { NgModule } from '@angular/core';\r\nimport { ButtonComponent } from './lib/button/button';\r\nimport { AccordionComponent } from './lib/accordion/accordion';\r\nimport { HeaderNavComponent } from './lib/header-nav/header-nav';\r\nimport { SidebarComponent } from './lib/sidebar/sidebar';\r\n\r\nexport { ButtonComponent } from './lib/button/button';\r\nexport type { ButtonVariant } from './lib/button/button';\r\nexport { AccordionComponent } from './lib/accordion/accordion';\r\nexport type { AccordionItem, AccordionButton, AccordionVariant, NestedAccordionItem } from './lib/accordion/accordion-types';\r\nexport { HeaderNavComponent } from './lib/header-nav/header-nav';\r\nexport type { HeaderNavLevel, NavAction } from './lib/header-nav/header-nav';\r\nexport { SidebarComponent } from './lib/sidebar/sidebar';\r\nexport type { SidebarItem } from './lib/sidebar/sidebar';\r\n\r\n@NgModule({\r\n imports: [ButtonComponent, AccordionComponent, HeaderNavComponent, SidebarComponent],\r\n exports: [ButtonComponent, AccordionComponent, HeaderNavComponent, SidebarComponent],\r\n})\r\nexport class CompsychAngularModule {}\r\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-types.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/accordion/accordion-types.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"accordion-types.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/accordion/accordion-types.ts"],"names":[],"mappings":"","sourcesContent":["/**\r\n * Visual style variant applied to the accordion and all its items.\r\n * - `'default'` — standard card style with borders and a background surface.\r\n * - `'level-2'` — compact nested style used inside another accordion panel.\r\n * - `'text-only'` — no card chrome; plain text trigger rows.\r\n */\r\nexport type AccordionVariant = 'default' | 'level-2' | 'text-only';\r\n\r\n/** An action button rendered at the bottom of an accordion panel. */\r\nexport interface AccordionButton {\r\n /** Text displayed on the button. */\r\n label: string;\r\n /** Called when the button is clicked. */\r\n action: () => void;\r\n}\r\n\r\n/**\r\n * A simplified accordion item used inside `AccordionItem.nestedContent`.\r\n * Identical to `AccordionItem` but without `buttons` and `nestedContent` —\r\n * nesting beyond one level is not supported.\r\n */\r\nexport type NestedAccordionItem = Omit<AccordionItem, 'buttons' | 'nestedContent'>;\r\n\r\n/** Data model for a single expandable row in a `compsych-accordion`. */\r\nexport interface AccordionItem {\r\n /** Text shown in the trigger button header. */\r\n title: string;\r\n /** HTML string rendered in the panel body when expanded. Sanitized before display. Omit when using `nestedContent`. */\r\n content?: string;\r\n /** Lucide icon name (kebab-case) shown to the left of the title, e.g. `'globe'` or `'folder'`. */\r\n icon?: string;\r\n /** Whether this panel starts expanded on initial render. Defaults to false. */\r\n expanded?: boolean;\r\n /** Action buttons rendered at the bottom of the panel, below `content`. */\r\n buttons?: AccordionButton[];\r\n /** Items rendered as a nested level-2 accordion inside this panel. Cannot contain `buttons` or further nesting. */\r\n nestedContent?: NestedAccordionItem[];\r\n}\r\n"]}
|
|
@@ -8,17 +8,29 @@ let nextId = 0;
|
|
|
8
8
|
// AccordionItemComponent is defined before AccordionComponent because AccordionComponent
|
|
9
9
|
// uses it directly in its imports. AccordionItemComponent references AccordionComponent
|
|
10
10
|
// via forwardRef to break the forward-reference cycle within this file.
|
|
11
|
+
/**
|
|
12
|
+
* A single expandable row used internally by `AccordionComponent`.
|
|
13
|
+
* Consumers should use `compsych-accordion` with an `items` array rather
|
|
14
|
+
* than placing `compsych-accordion-item` elements directly.
|
|
15
|
+
*/
|
|
11
16
|
export class AccordionItemComponent {
|
|
12
17
|
sanitizer;
|
|
13
18
|
el;
|
|
14
19
|
triggerId = `accordion-trigger-${nextId}`;
|
|
15
20
|
panelId = `accordion-panel-${nextId++}`;
|
|
21
|
+
/** Text shown in the trigger button header. */
|
|
16
22
|
title = '';
|
|
23
|
+
/** HTML string rendered in the panel body when expanded. Sanitized before display. */
|
|
17
24
|
content = '';
|
|
25
|
+
/** Action buttons rendered at the bottom of the panel. */
|
|
18
26
|
buttons = [];
|
|
27
|
+
/** Items rendered as a nested level-2 accordion inside this panel. */
|
|
19
28
|
nestedContent = [];
|
|
29
|
+
/** Visual style — inherited from the parent AccordionComponent. */
|
|
20
30
|
variant = 'default';
|
|
31
|
+
/** True when this item is rendered inside a parent accordion panel. Adds nested styling. Set automatically by AccordionComponent — do not set directly. */
|
|
21
32
|
nested = false;
|
|
33
|
+
/** Lucide icon name (kebab-case) shown to the left of the title. Pass an empty string to show no icon. */
|
|
22
34
|
set icon(name) {
|
|
23
35
|
const svg = LUCIDE_ICONS[name];
|
|
24
36
|
this.iconSvg = svg ? this.sanitizer.bypassSecurityTrustHtml(svg) : null;
|
|
@@ -31,9 +43,11 @@ export class AccordionItemComponent {
|
|
|
31
43
|
this.sanitizer = sanitizer;
|
|
32
44
|
this.el = el;
|
|
33
45
|
}
|
|
46
|
+
/** Whether this panel is currently open. Supports boolean coercion (e.g. `[expanded]="true"`). */
|
|
34
47
|
get expanded() { return this._expanded; }
|
|
35
48
|
set expanded(value) { this._expanded = coerceBooleanProperty(value); }
|
|
36
49
|
_expanded = false;
|
|
50
|
+
/** Emitted when the panel is toggled. `true` = opening, `false` = closing. */
|
|
37
51
|
expandedChange = new EventEmitter();
|
|
38
52
|
toggle() {
|
|
39
53
|
this._expanded = !this._expanded;
|
|
@@ -70,80 +84,80 @@ export class AccordionItemComponent {
|
|
|
70
84
|
}
|
|
71
85
|
}
|
|
72
86
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AccordionItemComponent, deps: [{ token: i1.DomSanitizer }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
73
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: AccordionItemComponent, isStandalone: true, selector: "compsych-accordion-item", inputs: { title: "title", content: "content", buttons: "buttons", nestedContent: "nestedContent", variant: "variant", nested: "nested", icon: "icon", expanded: "expanded" }, outputs: { expandedChange: "expandedChange" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "class.level-2": "variant === \"level-2\"", "class.text-only": "variant === \"text-only\"", "class.nested": "nested" }, classAttribute: "compsych-accordion-item" }, ngImport: i0, template: `
|
|
74
|
-
<button
|
|
75
|
-
class="trigger"
|
|
76
|
-
type="button"
|
|
77
|
-
[id]="triggerId"
|
|
78
|
-
[attr.aria-expanded]="expanded"
|
|
79
|
-
[attr.aria-controls]="panelId"
|
|
80
|
-
(click)="toggle()"
|
|
81
|
-
>
|
|
82
|
-
@if (iconSvg) {
|
|
83
|
-
<span class="trigger-leading-icon" [innerHTML]="iconSvg" aria-hidden="true"></span>
|
|
84
|
-
}
|
|
85
|
-
<span class="trigger-label">{{ title }}</span>
|
|
86
|
-
<span class="trigger-toggle material-symbols-rounded" aria-hidden="true">{{ expanded ? 'remove' : 'add' }}</span>
|
|
87
|
-
</button>
|
|
88
|
-
<div
|
|
89
|
-
[id]="panelId"
|
|
90
|
-
role="region"
|
|
91
|
-
[attr.aria-labelledby]="triggerId"
|
|
92
|
-
[hidden]="!expanded"
|
|
93
|
-
>
|
|
94
|
-
@if (content) {
|
|
95
|
-
<div class="content" [innerHTML]="sanitizedContent"></div>
|
|
96
|
-
}
|
|
97
|
-
@if (nestedContent.length) {
|
|
98
|
-
<compsych-accordion [items]="nestedContent" variant="level-2" [nested]="true" />
|
|
99
|
-
}
|
|
100
|
-
@if (buttons.length) {
|
|
101
|
-
<div class="actions">
|
|
102
|
-
@for (btn of buttons; track btn.label) {
|
|
103
|
-
<compsych-button [label]="btn.label" variant="filled" size="small" (click)="btn.action()" />
|
|
104
|
-
}
|
|
105
|
-
</div>
|
|
106
|
-
}
|
|
107
|
-
</div>
|
|
87
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: AccordionItemComponent, isStandalone: true, selector: "compsych-accordion-item", inputs: { title: "title", content: "content", buttons: "buttons", nestedContent: "nestedContent", variant: "variant", nested: "nested", icon: "icon", expanded: "expanded" }, outputs: { expandedChange: "expandedChange" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "class.level-2": "variant === \"level-2\"", "class.text-only": "variant === \"text-only\"", "class.nested": "nested" }, classAttribute: "compsych-accordion-item" }, ngImport: i0, template: `
|
|
88
|
+
<button
|
|
89
|
+
class="trigger"
|
|
90
|
+
type="button"
|
|
91
|
+
[id]="triggerId"
|
|
92
|
+
[attr.aria-expanded]="expanded"
|
|
93
|
+
[attr.aria-controls]="panelId"
|
|
94
|
+
(click)="toggle()"
|
|
95
|
+
>
|
|
96
|
+
@if (iconSvg) {
|
|
97
|
+
<span class="trigger-leading-icon" [innerHTML]="iconSvg" aria-hidden="true"></span>
|
|
98
|
+
}
|
|
99
|
+
<span class="trigger-label">{{ title }}</span>
|
|
100
|
+
<span class="trigger-toggle material-symbols-rounded" aria-hidden="true">{{ expanded ? 'remove' : 'add' }}</span>
|
|
101
|
+
</button>
|
|
102
|
+
<div
|
|
103
|
+
[id]="panelId"
|
|
104
|
+
role="region"
|
|
105
|
+
[attr.aria-labelledby]="triggerId"
|
|
106
|
+
[hidden]="!expanded"
|
|
107
|
+
>
|
|
108
|
+
@if (content) {
|
|
109
|
+
<div class="content" [innerHTML]="sanitizedContent"></div>
|
|
110
|
+
}
|
|
111
|
+
@if (nestedContent.length) {
|
|
112
|
+
<compsych-accordion [items]="nestedContent" variant="level-2" [nested]="true" />
|
|
113
|
+
}
|
|
114
|
+
@if (buttons.length) {
|
|
115
|
+
<div class="actions">
|
|
116
|
+
@for (btn of buttons; track btn.label) {
|
|
117
|
+
<compsych-button [label]="btn.label" variant="filled" size="small" (click)="btn.action()" />
|
|
118
|
+
}
|
|
119
|
+
</div>
|
|
120
|
+
}
|
|
121
|
+
</div>
|
|
108
122
|
`, isInline: true, styles: [":host{display:block;border-bottom:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline-variant)}:host:first-child{border-top:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline-variant)}.trigger{display:flex;align-items:center;gap:var(--spacing-padding-sys-padding-8);width:100%;padding:var(--spacing-padding-sys-padding-16) var(--spacing-padding-sys-padding-24);background:none;border:none;cursor:pointer;text-align:left;color:var(--surface-surface-sys-on-surface);font-family:var(--font-core-title);font-size:var(--size-core-lg);font-weight:var(--label-large-sys-font-weight);line-height:var(--size-core-2xl);letter-spacing:var(--spacing-core-spacing-0);position:relative;overflow:hidden;border-radius:0}.trigger:before{content:\"\";position:absolute;inset:0;background-color:currentColor;opacity:0;transition:opacity .15s ease;pointer-events:none}.trigger:hover:before{opacity:.08}.trigger:active:before{opacity:.1}.trigger-label{flex:1;min-width:0}.trigger-leading-icon{display:flex;align-items:center;justify-content:center;width:var(--sys-size-sm);height:var(--sys-size-sm);color:var(--surface-surface-sys-on-surface);flex-shrink:0}.trigger-leading-icon svg{width:100%;height:100%}.trigger-toggle{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:1.5rem;height:1.5rem;font-size:1rem;line-height:1;color:var(--surface-surface-sys-on-surface-variant);background-color:var(--surface-surface-container-sys-surface-container);border-radius:var(--border-radius-sys-radius-full);box-sizing:border-box}.trigger[aria-expanded=true] .trigger-toggle{color:var(--add-on-primary-fixed-sys-on-primary-fixed);background-color:var(--add-on-primary-fixed-sys-primary-fixed-dim)}.actions{display:flex;flex-wrap:wrap;gap:var(--spacing-padding-sys-padding-12);align-items:center;justify-content:flex-end;padding:var(--spacing-padding-sys-padding-16) var(--spacing-padding-sys-padding-24);border-top:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline)}:host(.level-2){border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline-variant);border-radius:0;overflow:hidden}:host(.level-2):first-child{border-radius:var(--border-radius-sys-radius-sm) var(--border-radius-sys-radius-sm) 0 0}:host(.level-2):last-child{border-radius:0 0 var(--border-radius-sys-radius-sm) var(--border-radius-sys-radius-sm)}:host(.level-2):only-child{border-radius:var(--border-radius-sys-radius-sm)}:host(.level-2.nested){border-radius:var(--border-radius-sys-radius-sm)}:host(.level-2) .trigger{padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-16) var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-16);gap:var(--spacing-padding-sys-padding-4);font-size:var(--size-core-base);line-height:var(--size-core-xl)}:host(.level-2) .actions{padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24)}:host(.level-2) .content{padding:0 var(--spacing-padding-sys-padding-24) var(--spacing-padding-sys-padding-8)}:host(.text-only){border:none}:host(.text-only) .trigger{padding:var(--spacing-padding-sys-padding-8) 0;font-size:var(--size-core-base);font-weight:var(--label-large-sys-font-weight-emphasized);line-height:var(--size-core-xl)}:host(.text-only) .trigger-leading-icon{display:none}:host(.text-only) .trigger-toggle{order:-1;color:var(--surface-surface-sys-on-surface-variant);background-color:var(--surface-surface-container-sys-surface-container)}.content{padding:0 var(--spacing-padding-sys-padding-16) var(--spacing-padding-sys-padding-16);color:var(--surface-surface-sys-on-surface-variant);font-family:var(--font-core-body);font-size:var(--size-core-base);font-weight:var(--label-large-sys-font-weight);line-height:var(--size-core-xl);letter-spacing:var(--spacing-core-spacing-0)}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => ButtonComponent), selector: "compsych-button", inputs: ["label", "variant", "size", "icon", "iconOnly", "disabled"] }, { kind: "component", type: i0.forwardRef(() => AccordionComponent), selector: "compsych-accordion", inputs: ["items", "variant", "nested", "multi"] }] });
|
|
109
123
|
}
|
|
110
124
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AccordionItemComponent, decorators: [{
|
|
111
125
|
type: Component,
|
|
112
|
-
args: [{ selector: 'compsych-accordion-item', standalone: true, imports: [ButtonComponent, forwardRef(() => AccordionComponent)], template: `
|
|
113
|
-
<button
|
|
114
|
-
class="trigger"
|
|
115
|
-
type="button"
|
|
116
|
-
[id]="triggerId"
|
|
117
|
-
[attr.aria-expanded]="expanded"
|
|
118
|
-
[attr.aria-controls]="panelId"
|
|
119
|
-
(click)="toggle()"
|
|
120
|
-
>
|
|
121
|
-
@if (iconSvg) {
|
|
122
|
-
<span class="trigger-leading-icon" [innerHTML]="iconSvg" aria-hidden="true"></span>
|
|
123
|
-
}
|
|
124
|
-
<span class="trigger-label">{{ title }}</span>
|
|
125
|
-
<span class="trigger-toggle material-symbols-rounded" aria-hidden="true">{{ expanded ? 'remove' : 'add' }}</span>
|
|
126
|
-
</button>
|
|
127
|
-
<div
|
|
128
|
-
[id]="panelId"
|
|
129
|
-
role="region"
|
|
130
|
-
[attr.aria-labelledby]="triggerId"
|
|
131
|
-
[hidden]="!expanded"
|
|
132
|
-
>
|
|
133
|
-
@if (content) {
|
|
134
|
-
<div class="content" [innerHTML]="sanitizedContent"></div>
|
|
135
|
-
}
|
|
136
|
-
@if (nestedContent.length) {
|
|
137
|
-
<compsych-accordion [items]="nestedContent" variant="level-2" [nested]="true" />
|
|
138
|
-
}
|
|
139
|
-
@if (buttons.length) {
|
|
140
|
-
<div class="actions">
|
|
141
|
-
@for (btn of buttons; track btn.label) {
|
|
142
|
-
<compsych-button [label]="btn.label" variant="filled" size="small" (click)="btn.action()" />
|
|
143
|
-
}
|
|
144
|
-
</div>
|
|
145
|
-
}
|
|
146
|
-
</div>
|
|
126
|
+
args: [{ selector: 'compsych-accordion-item', standalone: true, imports: [ButtonComponent, forwardRef(() => AccordionComponent)], template: `
|
|
127
|
+
<button
|
|
128
|
+
class="trigger"
|
|
129
|
+
type="button"
|
|
130
|
+
[id]="triggerId"
|
|
131
|
+
[attr.aria-expanded]="expanded"
|
|
132
|
+
[attr.aria-controls]="panelId"
|
|
133
|
+
(click)="toggle()"
|
|
134
|
+
>
|
|
135
|
+
@if (iconSvg) {
|
|
136
|
+
<span class="trigger-leading-icon" [innerHTML]="iconSvg" aria-hidden="true"></span>
|
|
137
|
+
}
|
|
138
|
+
<span class="trigger-label">{{ title }}</span>
|
|
139
|
+
<span class="trigger-toggle material-symbols-rounded" aria-hidden="true">{{ expanded ? 'remove' : 'add' }}</span>
|
|
140
|
+
</button>
|
|
141
|
+
<div
|
|
142
|
+
[id]="panelId"
|
|
143
|
+
role="region"
|
|
144
|
+
[attr.aria-labelledby]="triggerId"
|
|
145
|
+
[hidden]="!expanded"
|
|
146
|
+
>
|
|
147
|
+
@if (content) {
|
|
148
|
+
<div class="content" [innerHTML]="sanitizedContent"></div>
|
|
149
|
+
}
|
|
150
|
+
@if (nestedContent.length) {
|
|
151
|
+
<compsych-accordion [items]="nestedContent" variant="level-2" [nested]="true" />
|
|
152
|
+
}
|
|
153
|
+
@if (buttons.length) {
|
|
154
|
+
<div class="actions">
|
|
155
|
+
@for (btn of buttons; track btn.label) {
|
|
156
|
+
<compsych-button [label]="btn.label" variant="filled" size="small" (click)="btn.action()" />
|
|
157
|
+
}
|
|
158
|
+
</div>
|
|
159
|
+
}
|
|
160
|
+
</div>
|
|
147
161
|
`, host: {
|
|
148
162
|
class: 'compsych-accordion-item',
|
|
149
163
|
'[class.level-2]': 'variant === "level-2"',
|
|
@@ -173,9 +187,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
173
187
|
args: ['keydown', ['$event']]
|
|
174
188
|
}] } });
|
|
175
189
|
export class AccordionComponent {
|
|
190
|
+
/** Items to render as expandable rows. */
|
|
176
191
|
items = [];
|
|
192
|
+
/** Visual style applied to all items. `'default'` is the standard card style; `'level-2'` is compact nested style; `'text-only'` removes card chrome. */
|
|
177
193
|
variant = 'default';
|
|
194
|
+
/** Set automatically when this accordion is rendered inside another accordion panel. Do not set directly. */
|
|
178
195
|
nested = false;
|
|
196
|
+
/** When true, multiple panels can be open simultaneously. Defaults to false — opening one panel closes any others. Supports boolean coercion. */
|
|
179
197
|
get multi() { return this._multi; }
|
|
180
198
|
set multi(value) { this._multi = coerceBooleanProperty(value); }
|
|
181
199
|
_multi = false;
|
|
@@ -192,38 +210,38 @@ export class AccordionComponent {
|
|
|
192
210
|
this.expandedSet = new Set(this.expandedSet);
|
|
193
211
|
}
|
|
194
212
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AccordionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
195
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: AccordionComponent, isStandalone: true, selector: "compsych-accordion", inputs: { items: "items", variant: "variant", nested: "nested", multi: "multi" }, host: { properties: { "class.level-2": "variant === \"level-2\"", "class.text-only": "variant === \"text-only\"", "class.nested": "nested" } }, ngImport: i0, template: `
|
|
196
|
-
@for (item of items; track $index; let i = $index) {
|
|
197
|
-
<compsych-accordion-item
|
|
198
|
-
[title]="item.title"
|
|
199
|
-
[content]="item.content ?? ''"
|
|
200
|
-
[icon]="item.icon ?? ''"
|
|
201
|
-
[buttons]="item.buttons ?? []"
|
|
202
|
-
[nestedContent]="item.nestedContent ?? []"
|
|
203
|
-
[variant]="variant"
|
|
204
|
-
[nested]="nested"
|
|
205
|
-
[expanded]="expandedSet.has(i)"
|
|
206
|
-
(expandedChange)="onExpandedChange(i, $event)"
|
|
207
|
-
/>
|
|
208
|
-
}
|
|
213
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: AccordionComponent, isStandalone: true, selector: "compsych-accordion", inputs: { items: "items", variant: "variant", nested: "nested", multi: "multi" }, host: { properties: { "class.level-2": "variant === \"level-2\"", "class.text-only": "variant === \"text-only\"", "class.nested": "nested" } }, ngImport: i0, template: `
|
|
214
|
+
@for (item of items; track $index; let i = $index) {
|
|
215
|
+
<compsych-accordion-item
|
|
216
|
+
[title]="item.title"
|
|
217
|
+
[content]="item.content ?? ''"
|
|
218
|
+
[icon]="item.icon ?? ''"
|
|
219
|
+
[buttons]="item.buttons ?? []"
|
|
220
|
+
[nestedContent]="item.nestedContent ?? []"
|
|
221
|
+
[variant]="variant"
|
|
222
|
+
[nested]="nested"
|
|
223
|
+
[expanded]="expandedSet.has(i)"
|
|
224
|
+
(expandedChange)="onExpandedChange(i, $event)"
|
|
225
|
+
/>
|
|
226
|
+
}
|
|
209
227
|
`, isInline: true, styles: [":host{display:block;background-color:var(--surface-surface-container-sys-surface-container-lowest);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline);border-radius:var(--border-radius-sys-radius-md);overflow:hidden}:host(.level-2){display:flex;flex-direction:column;background-color:transparent;border:none;border-radius:0;overflow:visible}:host(.level-2.nested){gap:var(--spacing-padding-sys-padding-12);padding:0 var(--spacing-padding-sys-padding-24) var(--spacing-padding-sys-padding-24)}:host(.text-only){background-color:transparent;border:none;border-radius:0;overflow:visible}\n"], dependencies: [{ kind: "component", type: AccordionItemComponent, selector: "compsych-accordion-item", inputs: ["title", "content", "buttons", "nestedContent", "variant", "nested", "icon", "expanded"], outputs: ["expandedChange"] }] });
|
|
210
228
|
}
|
|
211
229
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AccordionComponent, decorators: [{
|
|
212
230
|
type: Component,
|
|
213
|
-
args: [{ selector: 'compsych-accordion', standalone: true, imports: [AccordionItemComponent], template: `
|
|
214
|
-
@for (item of items; track $index; let i = $index) {
|
|
215
|
-
<compsych-accordion-item
|
|
216
|
-
[title]="item.title"
|
|
217
|
-
[content]="item.content ?? ''"
|
|
218
|
-
[icon]="item.icon ?? ''"
|
|
219
|
-
[buttons]="item.buttons ?? []"
|
|
220
|
-
[nestedContent]="item.nestedContent ?? []"
|
|
221
|
-
[variant]="variant"
|
|
222
|
-
[nested]="nested"
|
|
223
|
-
[expanded]="expandedSet.has(i)"
|
|
224
|
-
(expandedChange)="onExpandedChange(i, $event)"
|
|
225
|
-
/>
|
|
226
|
-
}
|
|
231
|
+
args: [{ selector: 'compsych-accordion', standalone: true, imports: [AccordionItemComponent], template: `
|
|
232
|
+
@for (item of items; track $index; let i = $index) {
|
|
233
|
+
<compsych-accordion-item
|
|
234
|
+
[title]="item.title"
|
|
235
|
+
[content]="item.content ?? ''"
|
|
236
|
+
[icon]="item.icon ?? ''"
|
|
237
|
+
[buttons]="item.buttons ?? []"
|
|
238
|
+
[nestedContent]="item.nestedContent ?? []"
|
|
239
|
+
[variant]="variant"
|
|
240
|
+
[nested]="nested"
|
|
241
|
+
[expanded]="expandedSet.has(i)"
|
|
242
|
+
(expandedChange)="onExpandedChange(i, $event)"
|
|
243
|
+
/>
|
|
244
|
+
}
|
|
227
245
|
`, host: {
|
|
228
246
|
'[class.level-2]': 'variant === "level-2"',
|
|
229
247
|
'[class.text-only]': 'variant === "text-only"',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/accordion/accordion.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAET,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,KAAK,EACL,MAAM,EACN,eAAe,GAChB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAgB,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,8DAA8D,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;;;AAKnD,IAAI,MAAM,GAAG,CAAC,CAAC;AAEf,yFAAyF;AACzF,wFAAwF;AACxF,wEAAwE;AAiDxE,MAAM,OAAO,sBAAsB;IAwBd;IACA;IAxBV,SAAS,GAAG,qBAAqB,MAAM,EAAE,CAAC;IAC1C,OAAO,GAAG,mBAAmB,MAAM,EAAE,EAAE,CAAC;IAExC,KAAK,GAAG,EAAE,CAAC;IACX,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,GAAsB,EAAE,CAAC;IAChC,aAAa,GAA0B,EAAE,CAAC;IAC1C,OAAO,GAAqB,SAAS,CAAC;IACtC,MAAM,GAAG,KAAK,CAAC;IAExB,IACI,IAAI,CAAC,IAAY;QACnB,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1E,CAAC;IAED,OAAO,GAAoB,IAAI,CAAC;IAEhC,IAAI,gBAAgB;QAClB,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC3E,CAAC;IAED,YACmB,SAAuB,EACvB,EAA2B;QAD3B,cAAS,GAAT,SAAS,CAAc;QACvB,OAAE,GAAF,EAAE,CAAyB;IAC3C,CAAC;IAEJ,IACI,QAAQ,KAAc,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAClD,IAAI,QAAQ,CAAC,KAAmB,IAAI,IAAI,CAAC,SAAS,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5E,SAAS,GAAG,KAAK,CAAC;IAEhB,cAAc,GAAG,IAAI,YAAY,EAAW,CAAC;IAEvD,MAAM;QACJ,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;QACjC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC3C,CAAC;IAGD,SAAS,CAAC,KAAoB;QAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAC;QAC3C,IAAI,CAAC,CAAC,MAAM,YAAY,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO;QAE5F,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,aAAa,CAAC;QACnD,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CACzB,MAAM,CAAC,gBAAgB,CAAoB,mDAAmD,CAAC,CAChG,CAAC;QACF,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;QAEpB,QAAQ,KAAK,CAAC,GAAG,EAAE,CAAC;YAClB,KAAK,WAAW;gBAAE,UAAU,GAAG,YAAY,GAAG,CAAC,CAAC;gBAAC,MAAM;YACvD,KAAK,SAAS;gBAAI,UAAU,GAAG,YAAY,GAAG,CAAC,CAAC;gBAAC,MAAM;YACvD,KAAK,MAAM;gBAAO,UAAU,GAAG,CAAC,CAAC;gBAAC,MAAM;YACxC,KAAK,KAAK;gBAAQ,UAAU,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;gBAAC,MAAM;YAC1D,OAAO,CAAC,CAAC,OAAO;QAClB,CAAC;QAED,IAAI,UAAU,IAAI,CAAC,IAAI,UAAU,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;YACpD,QAAQ,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,CAAC;YAC7B,KAAK,CAAC,cAAc,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;wGAlEU,sBAAsB;4FAAtB,sBAAsB,0hBA5CvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCT,k0HApCS,eAAe,uJA4Id,kBAAkB;;4FA/FlB,sBAAsB;kBAhDlC,SAAS;+BACE,yBAAyB,cACvB,IAAI,WACP,CAAC,eAAe,EAAE,UAAU,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC,YACtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCT,QAEK;wBACJ,KAAK,EAAE,yBAAyB;wBAChC,iBAAiB,EAAE,uBAAuB;wBAC1C,mBAAmB,EAAE,yBAAyB;wBAC9C,gBAAgB,EAAE,QAAQ;qBAC3B;;sBAMA,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBAEL,KAAK;;sBAiBL,KAAK;;sBAKL,MAAM;;sBAON,YAAY;uBAAC,SAAS,EAAE,CAAC,QAAQ,CAAC;;AAuDrC,MAAM,OAAO,kBAAkB;IACpB,KAAK,GAAoB,EAAE,CAAC;IAC5B,OAAO,GAAqB,SAAS,CAAC;IACtC,MAAM,GAAG,KAAK,CAAC;IAExB,IACI,KAAK,KAAc,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5C,IAAI,KAAK,CAAC,KAAmB,IAAI,IAAI,CAAC,MAAM,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtE,MAAM,GAAG,KAAK,CAAC;IAEvB,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhC,gBAAgB,CAAC,KAAa,EAAE,UAAmB;QACjD,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;YAC3C,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/C,CAAC;wGApBU,kBAAkB;4FAAlB,kBAAkB,gTAtBnB;;;;;;;;;;;;;;GAcT,4qBAvFU,sBAAsB;;4FA+FtB,kBAAkB;kBA1B9B,SAAS;+BACE,oBAAoB,cAClB,IAAI,WACP,CAAC,sBAAsB,CAAC,YACvB;;;;;;;;;;;;;;GAcT,QAEK;wBACJ,iBAAiB,EAAE,uBAAuB;wBAC1C,mBAAmB,EAAE,yBAAyB;wBAC9C,gBAAgB,EAAE,QAAQ;qBAC3B;;sBAGA,KAAK;;sBACL,KAAK;;sBACL,KAAK;;sBAEL,KAAK","sourcesContent":["import {\n Component,\n ElementRef,\n EventEmitter,\n forwardRef,\n HostListener,\n Input,\n Output,\n SecurityContext,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { LUCIDE_ICONS } from '@compsych-ui-components/shared/assets/lucide-icons.generated';\nimport { ButtonComponent } from '../button/button';\nimport { AccordionButton, AccordionItem, AccordionVariant, NestedAccordionItem } from './accordion-types';\n\nexport type { AccordionButton, AccordionItem, AccordionVariant, NestedAccordionItem };\n\nlet nextId = 0;\n\n// AccordionItemComponent is defined before AccordionComponent because AccordionComponent\n// uses it directly in its imports. AccordionItemComponent references AccordionComponent\n// via forwardRef to break the forward-reference cycle within this file.\n@Component({\n selector: 'compsych-accordion-item',\n standalone: true,\n imports: [ButtonComponent, forwardRef(() => AccordionComponent)],\n template: `\n <button\n class=\"trigger\"\n type=\"button\"\n [id]=\"triggerId\"\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-controls]=\"panelId\"\n (click)=\"toggle()\"\n >\n @if (iconSvg) {\n <span class=\"trigger-leading-icon\" [innerHTML]=\"iconSvg\" aria-hidden=\"true\"></span>\n }\n <span class=\"trigger-label\">{{ title }}</span>\n <span class=\"trigger-toggle material-symbols-rounded\" aria-hidden=\"true\">{{ expanded ? 'remove' : 'add' }}</span>\n </button>\n <div\n [id]=\"panelId\"\n role=\"region\"\n [attr.aria-labelledby]=\"triggerId\"\n [hidden]=\"!expanded\"\n >\n @if (content) {\n <div class=\"content\" [innerHTML]=\"sanitizedContent\"></div>\n }\n @if (nestedContent.length) {\n <compsych-accordion [items]=\"nestedContent\" variant=\"level-2\" [nested]=\"true\" />\n }\n @if (buttons.length) {\n <div class=\"actions\">\n @for (btn of buttons; track btn.label) {\n <compsych-button [label]=\"btn.label\" variant=\"filled\" size=\"small\" (click)=\"btn.action()\" />\n }\n </div>\n }\n </div>\n `,\n styleUrl: './accordion-item.css',\n host: {\n class: 'compsych-accordion-item',\n '[class.level-2]': 'variant === \"level-2\"',\n '[class.text-only]': 'variant === \"text-only\"',\n '[class.nested]': 'nested',\n },\n})\nexport class AccordionItemComponent {\n readonly triggerId = `accordion-trigger-${nextId}`;\n readonly panelId = `accordion-panel-${nextId++}`;\n\n @Input() title = '';\n @Input() content = '';\n @Input() buttons: AccordionButton[] = [];\n @Input() nestedContent: NestedAccordionItem[] = [];\n @Input() variant: AccordionVariant = 'default';\n @Input() nested = false;\n\n @Input()\n set icon(name: string) {\n const svg = LUCIDE_ICONS[name];\n this.iconSvg = svg ? this.sanitizer.bypassSecurityTrustHtml(svg) : null;\n }\n\n iconSvg: SafeHtml | null = null;\n\n get sanitizedContent(): SafeHtml {\n return this.sanitizer.sanitize(SecurityContext.HTML, this.content) ?? '';\n }\n\n constructor(\n private readonly sanitizer: DomSanitizer,\n private readonly el: ElementRef<HTMLElement>,\n ) {}\n\n @Input()\n get expanded(): boolean { return this._expanded; }\n set expanded(value: BooleanInput) { this._expanded = coerceBooleanProperty(value); }\n private _expanded = false;\n\n @Output() expandedChange = new EventEmitter<boolean>();\n\n toggle(): void {\n this._expanded = !this._expanded;\n this.expandedChange.emit(this._expanded);\n }\n\n @HostListener('keydown', ['$event'])\n onKeydown(event: KeyboardEvent): void {\n const target = event.target as HTMLElement;\n if (!(target instanceof HTMLButtonElement) || !target.classList.contains('trigger')) return;\n\n const parent = this.el.nativeElement.parentElement;\n if (!parent) return;\n\n const triggers = Array.from(\n parent.querySelectorAll<HTMLButtonElement>(':scope > compsych-accordion-item > button.trigger'),\n );\n const currentIndex = triggers.indexOf(target);\n let focusIndex = -1;\n\n switch (event.key) {\n case 'ArrowDown': focusIndex = currentIndex + 1; break;\n case 'ArrowUp': focusIndex = currentIndex - 1; break;\n case 'Home': focusIndex = 0; break;\n case 'End': focusIndex = triggers.length - 1; break;\n default: return;\n }\n\n if (focusIndex >= 0 && focusIndex < triggers.length) {\n triggers[focusIndex].focus();\n event.preventDefault();\n }\n }\n}\n\n@Component({\n selector: 'compsych-accordion',\n standalone: true,\n imports: [AccordionItemComponent],\n template: `\n @for (item of items; track $index; let i = $index) {\n <compsych-accordion-item\n [title]=\"item.title\"\n [content]=\"item.content ?? ''\"\n [icon]=\"item.icon ?? ''\"\n [buttons]=\"item.buttons ?? []\"\n [nestedContent]=\"item.nestedContent ?? []\"\n [variant]=\"variant\"\n [nested]=\"nested\"\n [expanded]=\"expandedSet.has(i)\"\n (expandedChange)=\"onExpandedChange(i, $event)\"\n />\n }\n `,\n styleUrl: './accordion.css',\n host: {\n '[class.level-2]': 'variant === \"level-2\"',\n '[class.text-only]': 'variant === \"text-only\"',\n '[class.nested]': 'nested',\n },\n})\nexport class AccordionComponent {\n @Input() items: AccordionItem[] = [];\n @Input() variant: AccordionVariant = 'default';\n @Input() nested = false;\n\n @Input()\n get multi(): boolean { return this._multi; }\n set multi(value: BooleanInput) { this._multi = coerceBooleanProperty(value); }\n private _multi = false;\n\n expandedSet = new Set<number>();\n\n onExpandedChange(index: number, isExpanded: boolean): void {\n if (isExpanded) {\n if (!this._multi) this.expandedSet.clear();\n this.expandedSet.add(index);\n } else {\n this.expandedSet.delete(index);\n }\n this.expandedSet = new Set(this.expandedSet);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"accordion.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/accordion/accordion.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n Component,\r\n ElementRef,\r\n EventEmitter,\r\n forwardRef,\r\n HostListener,\r\n Input,\r\n Output,\r\n SecurityContext,\r\n} from '@angular/core';\r\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\r\nimport { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';\r\nimport { LUCIDE_ICONS } from '@compsych-ui-components/shared/assets/lucide-icons.generated';\r\nimport { ButtonComponent } from '../button/button';\r\nimport { AccordionButton, AccordionItem, AccordionVariant, NestedAccordionItem } from './accordion-types';\r\n\r\nexport type { AccordionButton, AccordionItem, AccordionVariant, NestedAccordionItem };\r\n\r\nlet nextId = 0;\r\n\r\n// AccordionItemComponent is defined before AccordionComponent because AccordionComponent\r\n// uses it directly in its imports. AccordionItemComponent references AccordionComponent\r\n// via forwardRef to break the forward-reference cycle within this file.\r\n@Component({\r\n selector: 'compsych-accordion-item',\r\n standalone: true,\r\n imports: [ButtonComponent, forwardRef(() => AccordionComponent)],\r\n template: `\r\n <button\r\n class=\"trigger\"\r\n type=\"button\"\r\n [id]=\"triggerId\"\r\n [attr.aria-expanded]=\"expanded\"\r\n [attr.aria-controls]=\"panelId\"\r\n (click)=\"toggle()\"\r\n >\r\n @if (iconSvg) {\r\n <span class=\"trigger-leading-icon\" [innerHTML]=\"iconSvg\" aria-hidden=\"true\"></span>\r\n }\r\n <span class=\"trigger-label\">{{ title }}</span>\r\n <span class=\"trigger-toggle material-symbols-rounded\" aria-hidden=\"true\">{{ expanded ? 'remove' : 'add' }}</span>\r\n </button>\r\n <div\r\n [id]=\"panelId\"\r\n role=\"region\"\r\n [attr.aria-labelledby]=\"triggerId\"\r\n [hidden]=\"!expanded\"\r\n >\r\n @if (content) {\r\n <div class=\"content\" [innerHTML]=\"sanitizedContent\"></div>\r\n }\r\n @if (nestedContent.length) {\r\n <compsych-accordion [items]=\"nestedContent\" variant=\"level-2\" [nested]=\"true\" />\r\n }\r\n @if (buttons.length) {\r\n <div class=\"actions\">\r\n @for (btn of buttons; track btn.label) {\r\n <compsych-button [label]=\"btn.label\" variant=\"filled\" size=\"small\" (click)=\"btn.action()\" />\r\n }\r\n </div>\r\n }\r\n </div>\r\n `,\r\n styleUrl: './accordion-item.css',\r\n host: {\r\n class: 'compsych-accordion-item',\r\n '[class.level-2]': 'variant === \"level-2\"',\r\n '[class.text-only]': 'variant === \"text-only\"',\r\n '[class.nested]': 'nested',\r\n },\r\n})\r\n/**\r\n * A single expandable row used internally by `AccordionComponent`.\r\n * Consumers should use `compsych-accordion` with an `items` array rather\r\n * than placing `compsych-accordion-item` elements directly.\r\n */\r\nexport class AccordionItemComponent {\r\n readonly triggerId = `accordion-trigger-${nextId}`;\r\n readonly panelId = `accordion-panel-${nextId++}`;\r\n\r\n /** Text shown in the trigger button header. */\r\n @Input() title = '';\r\n /** HTML string rendered in the panel body when expanded. Sanitized before display. */\r\n @Input() content = '';\r\n /** Action buttons rendered at the bottom of the panel. */\r\n @Input() buttons: AccordionButton[] = [];\r\n /** Items rendered as a nested level-2 accordion inside this panel. */\r\n @Input() nestedContent: NestedAccordionItem[] = [];\r\n /** Visual style — inherited from the parent AccordionComponent. */\r\n @Input() variant: AccordionVariant = 'default';\r\n /** True when this item is rendered inside a parent accordion panel. Adds nested styling. Set automatically by AccordionComponent — do not set directly. */\r\n @Input() nested = false;\r\n\r\n /** Lucide icon name (kebab-case) shown to the left of the title. Pass an empty string to show no icon. */\r\n @Input()\r\n set icon(name: string) {\r\n const svg = LUCIDE_ICONS[name];\r\n this.iconSvg = svg ? this.sanitizer.bypassSecurityTrustHtml(svg) : null;\r\n }\r\n\r\n iconSvg: SafeHtml | null = null;\r\n\r\n get sanitizedContent(): SafeHtml {\r\n return this.sanitizer.sanitize(SecurityContext.HTML, this.content) ?? '';\r\n }\r\n\r\n constructor(\r\n private readonly sanitizer: DomSanitizer,\r\n private readonly el: ElementRef<HTMLElement>,\r\n ) {}\r\n\r\n /** Whether this panel is currently open. Supports boolean coercion (e.g. `[expanded]=\"true\"`). */\r\n @Input()\r\n get expanded(): boolean { return this._expanded; }\r\n set expanded(value: BooleanInput) { this._expanded = coerceBooleanProperty(value); }\r\n private _expanded = false;\r\n\r\n /** Emitted when the panel is toggled. `true` = opening, `false` = closing. */\r\n @Output() expandedChange = new EventEmitter<boolean>();\r\n\r\n toggle(): void {\r\n this._expanded = !this._expanded;\r\n this.expandedChange.emit(this._expanded);\r\n }\r\n\r\n @HostListener('keydown', ['$event'])\r\n onKeydown(event: KeyboardEvent): void {\r\n const target = event.target as HTMLElement;\r\n if (!(target instanceof HTMLButtonElement) || !target.classList.contains('trigger')) return;\r\n\r\n const parent = this.el.nativeElement.parentElement;\r\n if (!parent) return;\r\n\r\n const triggers = Array.from(\r\n parent.querySelectorAll<HTMLButtonElement>(':scope > compsych-accordion-item > button.trigger'),\r\n );\r\n const currentIndex = triggers.indexOf(target);\r\n let focusIndex = -1;\r\n\r\n switch (event.key) {\r\n case 'ArrowDown': focusIndex = currentIndex + 1; break;\r\n case 'ArrowUp': focusIndex = currentIndex - 1; break;\r\n case 'Home': focusIndex = 0; break;\r\n case 'End': focusIndex = triggers.length - 1; break;\r\n default: return;\r\n }\r\n\r\n if (focusIndex >= 0 && focusIndex < triggers.length) {\r\n triggers[focusIndex].focus();\r\n event.preventDefault();\r\n }\r\n }\r\n}\r\n\r\n@Component({\r\n selector: 'compsych-accordion',\r\n standalone: true,\r\n imports: [AccordionItemComponent],\r\n template: `\r\n @for (item of items; track $index; let i = $index) {\r\n <compsych-accordion-item\r\n [title]=\"item.title\"\r\n [content]=\"item.content ?? ''\"\r\n [icon]=\"item.icon ?? ''\"\r\n [buttons]=\"item.buttons ?? []\"\r\n [nestedContent]=\"item.nestedContent ?? []\"\r\n [variant]=\"variant\"\r\n [nested]=\"nested\"\r\n [expanded]=\"expandedSet.has(i)\"\r\n (expandedChange)=\"onExpandedChange(i, $event)\"\r\n />\r\n }\r\n `,\r\n styleUrl: './accordion.css',\r\n host: {\r\n '[class.level-2]': 'variant === \"level-2\"',\r\n '[class.text-only]': 'variant === \"text-only\"',\r\n '[class.nested]': 'nested',\r\n },\r\n})\r\nexport class AccordionComponent {\r\n /** Items to render as expandable rows. */\r\n @Input() items: AccordionItem[] = [];\r\n /** Visual style applied to all items. `'default'` is the standard card style; `'level-2'` is compact nested style; `'text-only'` removes card chrome. */\r\n @Input() variant: AccordionVariant = 'default';\r\n /** Set automatically when this accordion is rendered inside another accordion panel. Do not set directly. */\r\n @Input() nested = false;\r\n /** When true, multiple panels can be open simultaneously. Defaults to false — opening one panel closes any others. Supports boolean coercion. */\r\n @Input()\r\n get multi(): boolean { return this._multi; }\r\n set multi(value: BooleanInput) { this._multi = coerceBooleanProperty(value); }\r\n private _multi = false;\r\n\r\n expandedSet = new Set<number>();\r\n\r\n onExpandedChange(index: number, isExpanded: boolean): void {\r\n if (isExpanded) {\r\n if (!this._multi) this.expandedSet.clear();\r\n this.expandedSet.add(index);\r\n } else {\r\n this.expandedSet.delete(index);\r\n }\r\n this.expandedSet = new Set(this.expandedSet);\r\n }\r\n}\r\n"]}
|
|
@@ -5,13 +5,19 @@ import * as i0 from "@angular/core";
|
|
|
5
5
|
export class ButtonComponent {
|
|
6
6
|
focusMonitor = inject(FocusMonitor);
|
|
7
7
|
elementRef = inject((ElementRef));
|
|
8
|
+
/** Text label rendered on the button. */
|
|
8
9
|
label = '';
|
|
10
|
+
/** Visual style of the button. Defaults to `'filled'`. */
|
|
9
11
|
variant = 'filled';
|
|
12
|
+
/** Button dimensions — controls height, padding, and font size. Defaults to `'small'`. */
|
|
10
13
|
size = 'small';
|
|
14
|
+
/** Material Symbols icon name shown to the left of the label (e.g. `'search'`, `'add'`). Omit to show label only. */
|
|
11
15
|
icon;
|
|
16
|
+
/** When true, hides the label and renders a square icon-only button. Requires `icon` to be set. Supports boolean coercion. */
|
|
12
17
|
get iconOnly() { return this._iconOnly; }
|
|
13
18
|
set iconOnly(value) { this._iconOnly = coerceBooleanProperty(value); }
|
|
14
19
|
_iconOnly = false;
|
|
20
|
+
/** When true, the button is non-interactive — `disabled` and `aria-disabled` attributes are set and `tabIndex` is set to `-1`. Supports boolean coercion. */
|
|
15
21
|
get disabled() { return this._disabled; }
|
|
16
22
|
set disabled(value) {
|
|
17
23
|
this._disabled = coerceBooleanProperty(value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/button/button.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,WAAW,EACX,YAAY,EACZ,KAAK,EACL,UAAU,EAGV,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAe,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"button.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/button/button.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n Component,\r\n HostBinding,\r\n HostListener,\r\n Input,\r\n ElementRef,\r\n AfterViewInit,\r\n OnDestroy,\r\n inject,\r\n} from '@angular/core';\r\nimport { FocusMonitor, FocusOrigin } from '@angular/cdk/a11y';\r\n\r\n/** Visual style of a `compsych-button`. Controls color, border, and elevation. */\r\nexport type ButtonVariant = 'filled' | 'tonal' | 'outlined' | 'elevated' | 'text' | 'danger' | 'danger-outlined';\r\nimport { coerceBooleanProperty, BooleanInput } from '@angular/cdk/coercion';\r\n\r\n@Component({\r\n selector: 'compsych-button',\r\n standalone: true,\r\n template: `\r\n @if (icon) {\r\n <span class=\"icon material-symbols-rounded\" aria-hidden=\"true\">{{ icon }}</span>\r\n }\r\n <span class=\"label\">{{ label }}</span>\r\n `,\r\n styleUrl: './button.css',\r\n host: {\r\n 'tabindex': '0',\r\n 'role': 'button',\r\n },\r\n})\r\nexport class ButtonComponent implements AfterViewInit, OnDestroy {\r\n private readonly focusMonitor = inject(FocusMonitor);\r\n private readonly elementRef = inject(ElementRef<HTMLElement>);\r\n\r\n /** Text label rendered on the button. */\r\n @Input() label = '';\r\n /** Visual style of the button. Defaults to `'filled'`. */\r\n @Input() variant: ButtonVariant = 'filled';\r\n /** Button dimensions — controls height, padding, and font size. Defaults to `'small'`. */\r\n @Input() size: 'small' | 'medium' | 'large' | 'xlarge' = 'small';\r\n /** Material Symbols icon name shown to the left of the label (e.g. `'search'`, `'add'`). Omit to show label only. */\r\n @Input() icon?: string;\r\n\r\n /** When true, hides the label and renders a square icon-only button. Requires `icon` to be set. Supports boolean coercion. */\r\n @Input()\r\n get iconOnly(): boolean { return this._iconOnly; }\r\n set iconOnly(value: BooleanInput) { this._iconOnly = coerceBooleanProperty(value); }\r\n private _iconOnly = false;\r\n\r\n /** When true, the button is non-interactive — `disabled` and `aria-disabled` attributes are set and `tabIndex` is set to `-1`. Supports boolean coercion. */\r\n @Input()\r\n get disabled(): boolean { return this._disabled; }\r\n set disabled(value: BooleanInput) {\r\n this._disabled = coerceBooleanProperty(value);\r\n this.elementRef.nativeElement.tabIndex = this._disabled ? -1 : 0;\r\n }\r\n private _disabled = false;\r\n\r\n protected _focused = false;\r\n protected _focusOrigin: FocusOrigin = null;\r\n\r\n @HostBinding('class') get hostClass(): string {\r\n return [\r\n this.variant,\r\n this.size,\r\n this._iconOnly ? 'icon-only' : '',\r\n this._focused && this._focusOrigin === 'keyboard' ? 'focus-keyboard' : '',\r\n ].filter(Boolean).join(' ');\r\n }\r\n\r\n @HostBinding('attr.aria-disabled') get ariaDisabled(): string | null {\r\n return this._disabled ? 'true' : null;\r\n }\r\n\r\n @HostBinding('attr.disabled') get attrDisabled(): '' | null {\r\n return this._disabled ? '' : null;\r\n }\r\n\r\n @HostListener('keydown.enter', ['$event'])\r\n onEnterKeydown(event: Event): void {\r\n if (!this._disabled) {\r\n event.preventDefault();\r\n this.elementRef.nativeElement.click();\r\n }\r\n }\r\n\r\n // Space: prevent scroll on keydown; fire action on keyup (matches native button behaviour).\r\n @HostListener('keydown.space', ['$event'])\r\n onSpaceKeydown(event: Event): void {\r\n event.preventDefault();\r\n }\r\n\r\n @HostListener('keyup.space')\r\n onSpaceKeyup(): void {\r\n if (!this._disabled) {\r\n this.elementRef.nativeElement.click();\r\n }\r\n }\r\n\r\n ngAfterViewInit(): void {\r\n this.focusMonitor.monitor(this.elementRef).subscribe(origin => {\r\n this._focused = !!origin;\r\n this._focusOrigin = origin;\r\n });\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.focusMonitor.stopMonitoring(this.elementRef);\r\n }\r\n}\r\n"]}
|