@compsych-ui-components/angular 6.1.1 → 7.0.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.
- package/esm2022/index.js.map +1 -1
- package/esm2022/lib/accordion/accordion.js +1 -1
- package/esm2022/lib/accordion/accordion.js.map +1 -1
- package/esm2022/lib/button/button.js +168 -69
- package/esm2022/lib/button/button.js.map +1 -1
- package/esm2022/lib/header-nav/header-nav.js +10 -9
- package/esm2022/lib/header-nav/header-nav.js.map +1 -1
- package/esm2022/lib/sidebar/sidebar.js +6 -6
- package/esm2022/lib/sidebar/sidebar.js.map +1 -1
- package/index.d.ts +1 -1
- package/lib/button/button.d.ts +49 -20
- package/lib/header-nav/header-nav.d.ts +9 -9
- package/lib/sidebar/sidebar.d.ts +2 -2
- 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';\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, ButtonSize,
|
|
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, ButtonSize, ButtonType } 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"]}
|
|
@@ -119,7 +119,7 @@ export class AccordionItemComponent {
|
|
|
119
119
|
</div>
|
|
120
120
|
}
|
|
121
121
|
</div>
|
|
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", "
|
|
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", "leadingIcon", "trailingIcon", "type", "ariaLabel", "iconOnly", "disabled", "loading", "fullWidth"] }, { kind: "component", type: i0.forwardRef(() => AccordionComponent), selector: "compsych-accordion", inputs: ["items", "variant", "nested", "multi"] }] });
|
|
123
123
|
}
|
|
124
124
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: AccordionItemComponent, decorators: [{
|
|
125
125
|
type: Component,
|
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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"]}
|
|
@@ -1,38 +1,62 @@
|
|
|
1
|
-
import { Component, Input, booleanAttribute } from '@angular/core';
|
|
1
|
+
import { ChangeDetectionStrategy, Component, HostBinding, Input, booleanAttribute, inject, } from '@angular/core';
|
|
2
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
3
|
+
import { LUCIDE_ICONS } from '@compsych-ui-components/shared/assets/lucide-icons.generated';
|
|
2
4
|
import * as i0 from "@angular/core";
|
|
5
|
+
/** Icon pixel size per button size (per design: sm/md 16, lg 20, xl 24). */
|
|
6
|
+
const ICON_SIZE_BY_BUTTON_SIZE = {
|
|
7
|
+
small: 16,
|
|
8
|
+
medium: 16,
|
|
9
|
+
large: 20,
|
|
10
|
+
xlarge: 24,
|
|
11
|
+
};
|
|
3
12
|
/**
|
|
4
13
|
* A themeable button that renders a native `<button>` element, so it is fully
|
|
5
|
-
* form-submittable and keyboard/screen-reader accessible out of the box.
|
|
14
|
+
* form-submittable and keyboard/screen-reader accessible out of the box. Icons
|
|
15
|
+
* are Lucide glyphs (matching the rest of the library); a button may carry a
|
|
16
|
+
* leading icon, a trailing icon, or both.
|
|
6
17
|
*/
|
|
7
18
|
export class ButtonComponent {
|
|
19
|
+
sanitizer = inject(DomSanitizer);
|
|
8
20
|
/** Text label rendered on the button. */
|
|
9
21
|
label = '';
|
|
10
22
|
/** Visual style of the button. Defaults to `'filled'`. */
|
|
11
23
|
variant = 'filled';
|
|
12
|
-
/** Button dimensions — controls height, padding, and
|
|
24
|
+
/** Button dimensions — controls height, padding, font size, and icon size. Defaults to `'small'`. */
|
|
13
25
|
size = 'small';
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
|
|
26
|
+
/** Lucide icon name shown before the label (e.g. `'house'`). Swapped for a spinner when loading. */
|
|
27
|
+
leadingIcon;
|
|
28
|
+
/** Lucide icon name shown after the label (e.g. `'chevron-right'`). Can be combined with `leadingIcon`. */
|
|
29
|
+
trailingIcon;
|
|
18
30
|
/** Native button type. Use `'submit'` inside a `<form>` to submit it. Defaults to `'button'`. */
|
|
19
31
|
type = 'button';
|
|
20
32
|
/**
|
|
21
|
-
* Accessible name. Required for icon-only buttons since the icon is decorative
|
|
22
|
-
*
|
|
33
|
+
* Accessible name. Required for icon-only buttons since the icon is decorative.
|
|
34
|
+
* When `iconOnly` or `loading` is set and this is omitted, `label` is used.
|
|
23
35
|
*/
|
|
24
36
|
ariaLabel;
|
|
25
|
-
/** Hides the label and renders a square icon-only button.
|
|
37
|
+
/** Hides the label and renders a square icon-only button. Uses `leadingIcon`. Supports boolean coercion. */
|
|
26
38
|
iconOnly = false;
|
|
27
|
-
/** Disables the button — non-interactive and
|
|
39
|
+
/** Disables the button — non-interactive and rendered in a neutral grey. Supports boolean coercion. */
|
|
28
40
|
disabled = false;
|
|
29
41
|
/**
|
|
30
|
-
* Loading state.
|
|
31
|
-
*
|
|
32
|
-
* variant's colours
|
|
33
|
-
* Supports boolean coercion.
|
|
42
|
+
* Loading state. Swaps the leading icon for a spinner and suppresses activation
|
|
43
|
+
* (native `disabled` blocks click/keyboard), while keeping the label and exposing
|
|
44
|
+
* `aria-busy="true"`. Retains the variant's colours. Supports boolean coercion.
|
|
34
45
|
*/
|
|
35
46
|
loading = false;
|
|
47
|
+
/** Stretches the button to the full width of its container. Supports boolean coercion. */
|
|
48
|
+
fullWidth = false;
|
|
49
|
+
/** Mirrors `fullWidth` onto the host so it can stretch. */
|
|
50
|
+
get hostFullWidth() {
|
|
51
|
+
return this.fullWidth;
|
|
52
|
+
}
|
|
53
|
+
/** Pre-sanitized icon markup, recomputed on input change (avoids re-sanitizing every CD cycle). */
|
|
54
|
+
leadingIconSvg = null;
|
|
55
|
+
trailingIconSvg = null;
|
|
56
|
+
ngOnChanges() {
|
|
57
|
+
this.leadingIconSvg = this.leadingIcon ? this.buildIconSvg(this.leadingIcon) : null;
|
|
58
|
+
this.trailingIconSvg = this.trailingIcon ? this.buildIconSvg(this.trailingIcon) : null;
|
|
59
|
+
}
|
|
36
60
|
/** Space-separated class list applied to the inner native button. */
|
|
37
61
|
get buttonClasses() {
|
|
38
62
|
return [
|
|
@@ -41,80 +65,149 @@ export class ButtonComponent {
|
|
|
41
65
|
this.size,
|
|
42
66
|
this.iconOnly ? 'icon-only' : '',
|
|
43
67
|
this.loading ? 'loading' : '',
|
|
68
|
+
this.fullWidth ? 'full-width' : '',
|
|
44
69
|
]
|
|
45
70
|
.filter(Boolean)
|
|
46
71
|
.join(' ');
|
|
47
72
|
}
|
|
48
|
-
/** Resolves the accessible name
|
|
73
|
+
/** Resolves the accessible name. Icon-only buttons have no visible text, so they fall back to `label`. */
|
|
49
74
|
get computedAriaLabel() {
|
|
50
75
|
if (this.ariaLabel) {
|
|
51
76
|
return this.ariaLabel;
|
|
52
77
|
}
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
// Loading keeps the visible label, so only icon-only needs an explicit name here.
|
|
79
|
+
return this.iconOnly ? this.label || null : null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Looks up a Lucide icon by name and injects an explicit width/height so the SVG
|
|
83
|
+
* renders at the correct size when injected via `[innerHTML]` (Angular's CSS
|
|
84
|
+
* encapsulation does not reach dynamically injected content).
|
|
85
|
+
*/
|
|
86
|
+
buildIconSvg(name) {
|
|
87
|
+
const size = ICON_SIZE_BY_BUTTON_SIZE[this.size];
|
|
88
|
+
const svg = (LUCIDE_ICONS[name] ?? '').replace('<svg ', `<svg width="${size}" height="${size}" `);
|
|
89
|
+
return this.sanitizer.bypassSecurityTrustHtml(svg);
|
|
90
|
+
}
|
|
91
|
+
/** M3 pressed ripple: pointer-down spawns a ripple at the pointer position. */
|
|
92
|
+
onPointerDown(event) {
|
|
93
|
+
if (this.disabled || this.loading || event.button !== 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const host = event.currentTarget;
|
|
97
|
+
const rect = host.getBoundingClientRect();
|
|
98
|
+
const x = event.clientX - rect.left;
|
|
99
|
+
const y = event.clientY - rect.top;
|
|
100
|
+
this.spawnRipple(host, x, y, this.farthestCornerDiameter(rect, x, y));
|
|
101
|
+
}
|
|
102
|
+
/** M3 pressed ripple: keyboard activation (Enter/Space) spawns a centered ripple. */
|
|
103
|
+
onKeyDown(event) {
|
|
104
|
+
if (this.disabled || this.loading || event.repeat) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (event.key !== 'Enter' && event.key !== ' ') {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const host = event.currentTarget;
|
|
111
|
+
const rect = host.getBoundingClientRect();
|
|
112
|
+
this.spawnRipple(host, rect.width / 2, rect.height / 2, Math.hypot(rect.width, rect.height));
|
|
113
|
+
}
|
|
114
|
+
/** Diameter that reaches the farthest corner from (x, y), so the ripple always covers the button. */
|
|
115
|
+
farthestCornerDiameter(rect, x, y) {
|
|
116
|
+
const corners = [
|
|
117
|
+
[0, 0],
|
|
118
|
+
[rect.width, 0],
|
|
119
|
+
[0, rect.height],
|
|
120
|
+
[rect.width, rect.height],
|
|
121
|
+
];
|
|
122
|
+
const maxRadius = Math.max(...corners.map(([cx, cy]) => Math.hypot(cx - x, cy - y)));
|
|
123
|
+
return maxRadius * 2;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Spawns an expanding ripple circle. Created imperatively with inline styles and
|
|
127
|
+
* animated via the Web Animations API — Angular's emulated encapsulation does not
|
|
128
|
+
* reach dynamically created nodes, so it cannot rely on the component stylesheet.
|
|
129
|
+
*/
|
|
130
|
+
spawnRipple(host, x, y, diameter) {
|
|
131
|
+
if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const ripple = document.createElement('span');
|
|
135
|
+
Object.assign(ripple.style, {
|
|
136
|
+
position: 'absolute',
|
|
137
|
+
borderRadius: '50%',
|
|
138
|
+
backgroundColor: 'currentColor',
|
|
139
|
+
pointerEvents: 'none',
|
|
140
|
+
width: `${diameter}px`,
|
|
141
|
+
height: `${diameter}px`,
|
|
142
|
+
left: `${x - diameter / 2}px`,
|
|
143
|
+
top: `${y - diameter / 2}px`,
|
|
144
|
+
});
|
|
145
|
+
host.appendChild(ripple);
|
|
146
|
+
const animation = ripple.animate([
|
|
147
|
+
{ transform: 'scale(0)', opacity: '0.16' },
|
|
148
|
+
{ transform: 'scale(1)', opacity: '0' },
|
|
149
|
+
], { duration: 450, easing: 'ease-out' });
|
|
150
|
+
animation.onfinish = () => ripple.remove();
|
|
56
151
|
}
|
|
57
152
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
58
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: ButtonComponent, isStandalone: true, selector: "compsych-button", inputs: { label: "label", variant: "variant", size: "size",
|
|
59
|
-
<button
|
|
60
|
-
[class]="buttonClasses"
|
|
61
|
-
[type]="type"
|
|
62
|
-
[disabled]="disabled || loading"
|
|
63
|
-
[attr.aria-busy]="loading ? 'true' : null"
|
|
64
|
-
[attr.aria-label]="computedAriaLabel"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`, isInline: true, styles: [":host{display:inline-flex}.cmp-btn{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;margin:0;border:none;border-radius:var(--border-radius-sys-radius-full);background:none;color:inherit;font:inherit;cursor:pointer;white-space:nowrap;position:relative;overflow:hidden;appearance:none;-webkit-appearance:none}.cmp-btn:before{content:\"\";position:absolute;inset:0;border-radius:inherit;background-color:currentColor;opacity:0;transition:opacity .15s ease;pointer-events:none}.cmp-btn:hover:before{opacity:.08}.cmp-btn:active:before{opacity:.1}.cmp-btn:focus-visible{outline:var(--border-width-sys-stroke-thick) solid var(--add-on-primary-fixed-sys-on-primary-fixed-variant);outline-offset:var(--border-width-sys-stroke-thick)}.cmp-btn:focus-visible:before{opacity:.12}.cmp-btn:disabled{cursor:not-allowed;pointer-events:none}.cmp-btn:disabled:not(.loading){opacity:.38}.icon{font-size:inherit;line-height:1;-webkit-user-select:none;user-select:none}.cmp-btn.icon-only{padding:0;aspect-ratio:1}.cmp-btn.icon-only.small{width:2rem}.cmp-btn.icon-only.medium{width:2.5rem}.cmp-btn.icon-only.large{width:3rem}.cmp-btn.icon-only.xlarge{width:3.5rem}.cmp-btn.loading>:not(.spinner-overlay){visibility:hidden}.spinner-overlay{position:absolute;inset:0;display:inline-flex;align-items:center;justify-content:center}.spinner{animation:cmp-btn-spin 1s linear infinite}@keyframes cmp-btn-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.spinner{animation-duration:2.4s}}.cmp-btn.small{gap:var(--spacing-padding-sys-padding-4);height:2rem;padding:var(--spacing-padding-sys-padding-4) var(--spacing-padding-sys-padding-12);font-family:var(--label-small-sys-font-family);font-size:var(--label-small-sys-font-size);font-weight:var(--label-small-sys-font-weight-emphasized);line-height:var(--label-small-sys-line-height);letter-spacing:var(--label-small-sys-tracking)}.cmp-btn.medium{gap:var(--spacing-padding-sys-padding-8);height:2.5rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-16);font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);font-weight:var(--label-medium-sys-font-weight-emphasized);line-height:var(--label-medium-sys-line-height);letter-spacing:var(--label-medium-sys-tracking)}.cmp-btn.large{gap:var(--spacing-padding-sys-padding-8);height:3rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);font-weight:var(--label-large-sys-font-weight-emphasized);line-height:var(--label-large-sys-line-height);letter-spacing:var(--label-large-sys-tracking)}.cmp-btn.xlarge{gap:var(--spacing-padding-sys-padding-12);height:3.5rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);font-weight:var(--label-large-sys-font-weight-emphasized);line-height:var(--label-large-sys-line-height);letter-spacing:var(--label-large-sys-tracking)}.cmp-btn.filled{background-color:var(--accent-primary-sys-primary);color:var(--accent-primary-sys-on-primary)}.cmp-btn.tonal{background-color:var(--add-on-primary-fixed-sys-primary-fixed-dim);color:var(--add-on-primary-fixed-sys-on-primary-fixed)}.cmp-btn.outlined{background-color:transparent;color:var(--surface-surface-sys-on-surface);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline)}.cmp-btn.elevated{background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--surface-surface-sys-on-surface);box-shadow:0 .125rem .25rem #00000014,0 .25rem 1rem var(--transparent-neutral-sys-black-10)}.cmp-btn.text{background-color:transparent;color:var(--surface-surface-sys-on-surface);padding-inline:0}.cmp-btn.danger{background-color:var(--error-sys-error);color:var(--error-sys-on-error)}.cmp-btn.danger-outlined{background-color:transparent;color:var(--error-sys-error);border:var(--border-width-sys-stroke-thin) solid var(--error-sys-error-container)}@media(forced-colors:active){.cmp-btn{border:1px solid ButtonText;forced-color-adjust:none}.cmp-btn.filled,.cmp-btn.tonal,.cmp-btn.danger{background:ButtonText;color:ButtonFace}.cmp-btn:focus-visible{outline:2px solid Highlight;outline-offset:2px}.cmp-btn:disabled:not(.loading){color:GrayText;border-color:GrayText}}\n"] });
|
|
153
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: ButtonComponent, isStandalone: true, selector: "compsych-button", inputs: { label: "label", variant: "variant", size: "size", leadingIcon: "leadingIcon", trailingIcon: "trailingIcon", type: "type", ariaLabel: "ariaLabel", iconOnly: ["iconOnly", "iconOnly", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], loading: ["loading", "loading", booleanAttribute], fullWidth: ["fullWidth", "fullWidth", booleanAttribute] }, host: { properties: { "class.full-width": "this.hostFullWidth" } }, usesOnChanges: true, ngImport: i0, template: `
|
|
154
|
+
<button
|
|
155
|
+
[class]="buttonClasses"
|
|
156
|
+
[type]="type"
|
|
157
|
+
[disabled]="disabled || loading"
|
|
158
|
+
[attr.aria-busy]="loading ? 'true' : null"
|
|
159
|
+
[attr.aria-label]="computedAriaLabel"
|
|
160
|
+
(pointerdown)="onPointerDown($event)"
|
|
161
|
+
(keydown)="onKeyDown($event)"
|
|
162
|
+
>
|
|
163
|
+
@if (loading) {
|
|
164
|
+
<span class="icon spinner" aria-hidden="true"></span>
|
|
165
|
+
} @else if (leadingIcon) {
|
|
166
|
+
<span class="icon" aria-hidden="true" [innerHTML]="leadingIconSvg"></span>
|
|
167
|
+
}
|
|
168
|
+
@if (!iconOnly) {
|
|
169
|
+
<span class="label">{{ label }}<ng-content /></span>
|
|
170
|
+
}
|
|
171
|
+
@if (trailingIcon && !iconOnly) {
|
|
172
|
+
<span class="icon" aria-hidden="true" [innerHTML]="trailingIconSvg"></span>
|
|
173
|
+
}
|
|
174
|
+
</button>
|
|
175
|
+
`, isInline: true, styles: [":host{display:inline-flex}:host(.full-width){display:flex;width:100%}.cmp-btn{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;margin:0;border:none;border-radius:var(--border-radius-sys-radius-full);background:none;color:inherit;font:inherit;cursor:pointer;white-space:nowrap;position:relative;overflow:hidden;appearance:none;-webkit-appearance:none;transition:box-shadow .15s ease}.cmp-btn.full-width{width:100%}.cmp-btn:before{content:\"\";position:absolute;inset:0;border-radius:inherit;background-color:currentColor;opacity:0;transition:opacity .15s ease;pointer-events:none}.cmp-btn:hover:before{opacity:.08}.cmp-btn:active:before{opacity:.1}.cmp-btn.filled:hover:not(:disabled),.cmp-btn.danger:hover:not(:disabled){box-shadow:0 .25rem 1rem var(--transparent-neutral-sys-black-10),0 .125rem .25rem #00000014}.cmp-btn:focus-visible{outline:var(--border-width-sys-stroke-thick) solid var(--add-on-primary-fixed-sys-on-primary-fixed-variant);outline-offset:var(--border-width-sys-stroke-thick)}.cmp-btn:focus-visible:before{opacity:.12}.cmp-btn:disabled{pointer-events:none}.cmp-btn:disabled:not(.loading){cursor:not-allowed;background-color:var(--surface-surface-container-sys-surface-container-highest);color:var(--surface-surface-sys-on-surface);border-color:transparent;box-shadow:none;opacity:.48}.cmp-btn.outlined:disabled:not(.loading),.cmp-btn.text:disabled:not(.loading),.cmp-btn.danger-outlined:disabled:not(.loading){background-color:transparent}.cmp-btn.outlined:disabled:not(.loading),.cmp-btn.danger-outlined:disabled:not(.loading){border-color:var(--outline-sys-outline-variant)}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;line-height:0;-webkit-user-select:none;user-select:none;width:1rem;height:1rem}.cmp-btn.large .icon{width:1.25rem;height:1.25rem}.cmp-btn.xlarge .icon{width:1.5rem;height:1.5rem}.icon svg{width:100%;height:100%}.cmp-btn.icon-only{padding:0;aspect-ratio:1}.cmp-btn.icon-only.small{width:2rem}.cmp-btn.icon-only.medium{width:2.5rem}.cmp-btn.icon-only.large{width:3rem}.cmp-btn.icon-only.xlarge{width:3.5rem}.spinner{border:2px solid currentColor;border-right-color:transparent;border-radius:50%;box-sizing:border-box;animation:cmp-btn-spin 1s linear infinite}@keyframes cmp-btn-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.spinner{animation-duration:2.4s}}.cmp-btn.small{gap:var(--spacing-padding-sys-padding-4);height:2rem;padding:var(--spacing-padding-sys-padding-4) var(--spacing-padding-sys-padding-12);font-family:var(--label-small-sys-font-family);font-size:var(--label-small-sys-font-size);font-weight:var(--label-small-sys-font-weight-emphasized);line-height:var(--label-small-sys-line-height);letter-spacing:var(--label-small-sys-tracking)}.cmp-btn.medium{gap:var(--spacing-padding-sys-padding-8);height:2.5rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-16);font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);font-weight:var(--label-medium-sys-font-weight-emphasized);line-height:var(--label-medium-sys-line-height);letter-spacing:var(--label-medium-sys-tracking)}.cmp-btn.large{gap:var(--spacing-padding-sys-padding-8);height:3rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);font-weight:var(--label-large-sys-font-weight-emphasized);line-height:var(--label-large-sys-line-height);letter-spacing:var(--label-large-sys-tracking)}.cmp-btn.xlarge{gap:var(--spacing-padding-sys-padding-12);height:3.5rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);font-weight:var(--label-large-sys-font-weight-emphasized);line-height:var(--label-large-sys-line-height);letter-spacing:var(--label-large-sys-tracking)}.cmp-btn.filled{background-color:var(--accent-primary-sys-primary);color:var(--accent-primary-sys-on-primary)}.cmp-btn.tonal{background-color:var(--add-on-primary-fixed-sys-primary-fixed-dim);color:var(--add-on-primary-fixed-sys-on-primary-fixed)}.cmp-btn.outlined{background-color:transparent;color:var(--surface-surface-sys-on-surface);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline)}.cmp-btn.elevated{background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--surface-surface-sys-on-surface);box-shadow:0 .125rem .25rem #00000014,0 .25rem 1rem var(--transparent-neutral-sys-black-10)}.cmp-btn.text{background-color:transparent;color:var(--surface-surface-sys-on-surface);padding-inline:0}.cmp-btn.danger{background-color:var(--error-sys-error);color:var(--error-sys-on-error)}.cmp-btn.danger-outlined{background-color:transparent;color:var(--error-sys-error);border:var(--border-width-sys-stroke-thin) solid var(--error-sys-error-container)}@media(forced-colors:active){.cmp-btn{border:1px solid ButtonText;forced-color-adjust:none}.cmp-btn.filled,.cmp-btn.tonal,.cmp-btn.danger{background:ButtonText;color:ButtonFace}.cmp-btn:focus-visible{outline:2px solid Highlight;outline-offset:2px}.cmp-btn:disabled:not(.loading){color:GrayText;border-color:GrayText;background:ButtonFace}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
82
176
|
}
|
|
83
177
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
84
178
|
type: Component,
|
|
85
|
-
args: [{ selector: 'compsych-button', standalone: true, template: `
|
|
86
|
-
<button
|
|
87
|
-
[class]="buttonClasses"
|
|
88
|
-
[type]="type"
|
|
89
|
-
[disabled]="disabled || loading"
|
|
90
|
-
[attr.aria-busy]="loading ? 'true' : null"
|
|
91
|
-
[attr.aria-label]="computedAriaLabel"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
`, styles: [":host{display:inline-flex}.cmp-btn{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;margin:0;border:none;border-radius:var(--border-radius-sys-radius-full);background:none;color:inherit;font:inherit;cursor:pointer;white-space:nowrap;position:relative;overflow:hidden;appearance:none;-webkit-appearance:none}.cmp-btn:before{content:\"\";position:absolute;inset:0;border-radius:inherit;background-color:currentColor;opacity:0;transition:opacity .15s ease;pointer-events:none}.cmp-btn:hover:before{opacity:.08}.cmp-btn:active:before{opacity:.1}.cmp-btn:focus-visible{outline:var(--border-width-sys-stroke-thick) solid var(--add-on-primary-fixed-sys-on-primary-fixed-variant);outline-offset:var(--border-width-sys-stroke-thick)}.cmp-btn:focus-visible:before{opacity:.12}.cmp-btn:disabled{cursor:not-allowed;pointer-events:none}.cmp-btn:disabled:not(.loading){opacity:.38}.icon{font-size:inherit;line-height:1;-webkit-user-select:none;user-select:none}.cmp-btn.icon-only{padding:0;aspect-ratio:1}.cmp-btn.icon-only.small{width:2rem}.cmp-btn.icon-only.medium{width:2.5rem}.cmp-btn.icon-only.large{width:3rem}.cmp-btn.icon-only.xlarge{width:3.5rem}.cmp-btn.loading>:not(.spinner-overlay){visibility:hidden}.spinner-overlay{position:absolute;inset:0;display:inline-flex;align-items:center;justify-content:center}.spinner{animation:cmp-btn-spin 1s linear infinite}@keyframes cmp-btn-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.spinner{animation-duration:2.4s}}.cmp-btn.small{gap:var(--spacing-padding-sys-padding-4);height:2rem;padding:var(--spacing-padding-sys-padding-4) var(--spacing-padding-sys-padding-12);font-family:var(--label-small-sys-font-family);font-size:var(--label-small-sys-font-size);font-weight:var(--label-small-sys-font-weight-emphasized);line-height:var(--label-small-sys-line-height);letter-spacing:var(--label-small-sys-tracking)}.cmp-btn.medium{gap:var(--spacing-padding-sys-padding-8);height:2.5rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-16);font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);font-weight:var(--label-medium-sys-font-weight-emphasized);line-height:var(--label-medium-sys-line-height);letter-spacing:var(--label-medium-sys-tracking)}.cmp-btn.large{gap:var(--spacing-padding-sys-padding-8);height:3rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);font-weight:var(--label-large-sys-font-weight-emphasized);line-height:var(--label-large-sys-line-height);letter-spacing:var(--label-large-sys-tracking)}.cmp-btn.xlarge{gap:var(--spacing-padding-sys-padding-12);height:3.5rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);font-weight:var(--label-large-sys-font-weight-emphasized);line-height:var(--label-large-sys-line-height);letter-spacing:var(--label-large-sys-tracking)}.cmp-btn.filled{background-color:var(--accent-primary-sys-primary);color:var(--accent-primary-sys-on-primary)}.cmp-btn.tonal{background-color:var(--add-on-primary-fixed-sys-primary-fixed-dim);color:var(--add-on-primary-fixed-sys-on-primary-fixed)}.cmp-btn.outlined{background-color:transparent;color:var(--surface-surface-sys-on-surface);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline)}.cmp-btn.elevated{background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--surface-surface-sys-on-surface);box-shadow:0 .125rem .25rem #00000014,0 .25rem 1rem var(--transparent-neutral-sys-black-10)}.cmp-btn.text{background-color:transparent;color:var(--surface-surface-sys-on-surface);padding-inline:0}.cmp-btn.danger{background-color:var(--error-sys-error);color:var(--error-sys-on-error)}.cmp-btn.danger-outlined{background-color:transparent;color:var(--error-sys-error);border:var(--border-width-sys-stroke-thin) solid var(--error-sys-error-container)}@media(forced-colors:active){.cmp-btn{border:1px solid ButtonText;forced-color-adjust:none}.cmp-btn.filled,.cmp-btn.tonal,.cmp-btn.danger{background:ButtonText;color:ButtonFace}.cmp-btn:focus-visible{outline:2px solid Highlight;outline-offset:2px}.cmp-btn:disabled:not(.loading){color:GrayText;border-color:GrayText}}\n"] }]
|
|
179
|
+
args: [{ selector: 'compsych-button', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
180
|
+
<button
|
|
181
|
+
[class]="buttonClasses"
|
|
182
|
+
[type]="type"
|
|
183
|
+
[disabled]="disabled || loading"
|
|
184
|
+
[attr.aria-busy]="loading ? 'true' : null"
|
|
185
|
+
[attr.aria-label]="computedAriaLabel"
|
|
186
|
+
(pointerdown)="onPointerDown($event)"
|
|
187
|
+
(keydown)="onKeyDown($event)"
|
|
188
|
+
>
|
|
189
|
+
@if (loading) {
|
|
190
|
+
<span class="icon spinner" aria-hidden="true"></span>
|
|
191
|
+
} @else if (leadingIcon) {
|
|
192
|
+
<span class="icon" aria-hidden="true" [innerHTML]="leadingIconSvg"></span>
|
|
193
|
+
}
|
|
194
|
+
@if (!iconOnly) {
|
|
195
|
+
<span class="label">{{ label }}<ng-content /></span>
|
|
196
|
+
}
|
|
197
|
+
@if (trailingIcon && !iconOnly) {
|
|
198
|
+
<span class="icon" aria-hidden="true" [innerHTML]="trailingIconSvg"></span>
|
|
199
|
+
}
|
|
200
|
+
</button>
|
|
201
|
+
`, styles: [":host{display:inline-flex}:host(.full-width){display:flex;width:100%}.cmp-btn{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;margin:0;border:none;border-radius:var(--border-radius-sys-radius-full);background:none;color:inherit;font:inherit;cursor:pointer;white-space:nowrap;position:relative;overflow:hidden;appearance:none;-webkit-appearance:none;transition:box-shadow .15s ease}.cmp-btn.full-width{width:100%}.cmp-btn:before{content:\"\";position:absolute;inset:0;border-radius:inherit;background-color:currentColor;opacity:0;transition:opacity .15s ease;pointer-events:none}.cmp-btn:hover:before{opacity:.08}.cmp-btn:active:before{opacity:.1}.cmp-btn.filled:hover:not(:disabled),.cmp-btn.danger:hover:not(:disabled){box-shadow:0 .25rem 1rem var(--transparent-neutral-sys-black-10),0 .125rem .25rem #00000014}.cmp-btn:focus-visible{outline:var(--border-width-sys-stroke-thick) solid var(--add-on-primary-fixed-sys-on-primary-fixed-variant);outline-offset:var(--border-width-sys-stroke-thick)}.cmp-btn:focus-visible:before{opacity:.12}.cmp-btn:disabled{pointer-events:none}.cmp-btn:disabled:not(.loading){cursor:not-allowed;background-color:var(--surface-surface-container-sys-surface-container-highest);color:var(--surface-surface-sys-on-surface);border-color:transparent;box-shadow:none;opacity:.48}.cmp-btn.outlined:disabled:not(.loading),.cmp-btn.text:disabled:not(.loading),.cmp-btn.danger-outlined:disabled:not(.loading){background-color:transparent}.cmp-btn.outlined:disabled:not(.loading),.cmp-btn.danger-outlined:disabled:not(.loading){border-color:var(--outline-sys-outline-variant)}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;line-height:0;-webkit-user-select:none;user-select:none;width:1rem;height:1rem}.cmp-btn.large .icon{width:1.25rem;height:1.25rem}.cmp-btn.xlarge .icon{width:1.5rem;height:1.5rem}.icon svg{width:100%;height:100%}.cmp-btn.icon-only{padding:0;aspect-ratio:1}.cmp-btn.icon-only.small{width:2rem}.cmp-btn.icon-only.medium{width:2.5rem}.cmp-btn.icon-only.large{width:3rem}.cmp-btn.icon-only.xlarge{width:3.5rem}.spinner{border:2px solid currentColor;border-right-color:transparent;border-radius:50%;box-sizing:border-box;animation:cmp-btn-spin 1s linear infinite}@keyframes cmp-btn-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.spinner{animation-duration:2.4s}}.cmp-btn.small{gap:var(--spacing-padding-sys-padding-4);height:2rem;padding:var(--spacing-padding-sys-padding-4) var(--spacing-padding-sys-padding-12);font-family:var(--label-small-sys-font-family);font-size:var(--label-small-sys-font-size);font-weight:var(--label-small-sys-font-weight-emphasized);line-height:var(--label-small-sys-line-height);letter-spacing:var(--label-small-sys-tracking)}.cmp-btn.medium{gap:var(--spacing-padding-sys-padding-8);height:2.5rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-16);font-family:var(--label-medium-sys-font-family);font-size:var(--label-medium-sys-font-size);font-weight:var(--label-medium-sys-font-weight-emphasized);line-height:var(--label-medium-sys-line-height);letter-spacing:var(--label-medium-sys-tracking)}.cmp-btn.large{gap:var(--spacing-padding-sys-padding-8);height:3rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);font-weight:var(--label-large-sys-font-weight-emphasized);line-height:var(--label-large-sys-line-height);letter-spacing:var(--label-large-sys-tracking)}.cmp-btn.xlarge{gap:var(--spacing-padding-sys-padding-12);height:3.5rem;padding:var(--spacing-padding-sys-padding-8) var(--spacing-padding-sys-padding-24);font-family:var(--label-large-sys-font-family);font-size:var(--label-large-sys-font-size);font-weight:var(--label-large-sys-font-weight-emphasized);line-height:var(--label-large-sys-line-height);letter-spacing:var(--label-large-sys-tracking)}.cmp-btn.filled{background-color:var(--accent-primary-sys-primary);color:var(--accent-primary-sys-on-primary)}.cmp-btn.tonal{background-color:var(--add-on-primary-fixed-sys-primary-fixed-dim);color:var(--add-on-primary-fixed-sys-on-primary-fixed)}.cmp-btn.outlined{background-color:transparent;color:var(--surface-surface-sys-on-surface);border:var(--border-width-sys-stroke-thin) solid var(--outline-sys-outline)}.cmp-btn.elevated{background-color:var(--surface-surface-container-sys-surface-container-lowest);color:var(--surface-surface-sys-on-surface);box-shadow:0 .125rem .25rem #00000014,0 .25rem 1rem var(--transparent-neutral-sys-black-10)}.cmp-btn.text{background-color:transparent;color:var(--surface-surface-sys-on-surface);padding-inline:0}.cmp-btn.danger{background-color:var(--error-sys-error);color:var(--error-sys-on-error)}.cmp-btn.danger-outlined{background-color:transparent;color:var(--error-sys-error);border:var(--border-width-sys-stroke-thin) solid var(--error-sys-error-container)}@media(forced-colors:active){.cmp-btn{border:1px solid ButtonText;forced-color-adjust:none}.cmp-btn.filled,.cmp-btn.tonal,.cmp-btn.danger{background:ButtonText;color:ButtonFace}.cmp-btn:focus-visible{outline:2px solid Highlight;outline-offset:2px}.cmp-btn:disabled:not(.loading){color:GrayText;border-color:GrayText;background:ButtonFace}}\n"] }]
|
|
109
202
|
}], propDecorators: { label: [{
|
|
110
203
|
type: Input
|
|
111
204
|
}], variant: [{
|
|
112
205
|
type: Input
|
|
113
206
|
}], size: [{
|
|
114
207
|
type: Input
|
|
115
|
-
}],
|
|
208
|
+
}], leadingIcon: [{
|
|
116
209
|
type: Input
|
|
117
|
-
}],
|
|
210
|
+
}], trailingIcon: [{
|
|
118
211
|
type: Input
|
|
119
212
|
}], type: [{
|
|
120
213
|
type: Input
|
|
@@ -129,5 +222,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
129
222
|
}], loading: [{
|
|
130
223
|
type: Input,
|
|
131
224
|
args: [{ transform: booleanAttribute }]
|
|
225
|
+
}], fullWidth: [{
|
|
226
|
+
type: Input,
|
|
227
|
+
args: [{ transform: booleanAttribute }]
|
|
228
|
+
}], hostFullWidth: [{
|
|
229
|
+
type: HostBinding,
|
|
230
|
+
args: ['class.full-width']
|
|
132
231
|
}] } });
|
|
133
232
|
//# sourceMappingURL=button.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/button/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;;AAqBnE;;;GAGG;AA8BH,MAAM,OAAO,eAAe;IAC1B,yCAAyC;IAChC,KAAK,GAAG,EAAE,CAAC;IACpB,0DAA0D;IACjD,OAAO,GAAkB,QAAQ,CAAC;IAC3C,0FAA0F;IACjF,IAAI,GAAe,OAAO,CAAC;IACpC,gGAAgG;IACvF,IAAI,CAAU;IACvB,0EAA0E;IACjE,YAAY,GAAuB,SAAS,CAAC;IACtD,iGAAiG;IACxF,IAAI,GAAe,QAAQ,CAAC;IACrC;;;OAGG;IACM,SAAS,CAAU;IAE5B,yGAAyG;IACjE,QAAQ,GAAG,KAAK,CAAC;IACzD,mFAAmF;IAC3C,QAAQ,GAAG,KAAK,CAAC;IACzD;;;;;OAKG;IACqC,OAAO,GAAG,KAAK,CAAC;IAExD,qEAAqE;IACrE,IAAI,aAAa;QACf,OAAO;YACL,SAAS;YACT,IAAI,CAAC,OAAO;YACZ,IAAI,CAAC,IAAI;YACT,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE;YAChC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;SAC9B;aACE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IAED,6FAA6F;IAC7F,IAAI,iBAAiB;QACnB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;QACD,qFAAqF;QACrF,wFAAwF;QACxF,OAAO,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,CAAC;wGApDU,eAAe;4FAAf,eAAe,oOAoBN,gBAAgB,sCAEhB,gBAAgB,mCAOhB,gBAAgB,6BAvD1B;;;;;;;;;;;;;;;;;;;;;;;GAuBT;;4FAGU,eAAe;kBA7B3B,SAAS;+BACE,iBAAiB,cACf,IAAI,YACN;;;;;;;;;;;;;;;;;;;;;;;GAuBT;;sBAKA,KAAK;;sBAEL,KAAK;;sBAEL,KAAK;;sBAEL,KAAK;;sBAEL,KAAK;;sBAEL,KAAK;;sBAKL,KAAK;;sBAGL,KAAK;uBAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE;;sBAErC,KAAK;uBAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE;;sBAOrC,KAAK;uBAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE","sourcesContent":["import { Component, Input, booleanAttribute } from '@angular/core';\r\n\r\n/** Visual style of a `compsych-button`. Controls color, border, and elevation. */\r\nexport type ButtonVariant =\r\n | 'filled'\r\n | 'tonal'\r\n | 'outlined'\r\n | 'elevated'\r\n | 'text'\r\n | 'danger'\r\n | 'danger-outlined';\r\n\r\n/** Button dimensions — controls height, padding, and font size. */\r\nexport type ButtonSize = 'small' | 'medium' | 'large' | 'xlarge';\r\n\r\n/** Placement of `icon` relative to the label. */\r\nexport type ButtonIconPosition = 'leading' | 'trailing';\r\n\r\n/** Native button behaviour. `'submit'`/`'reset'` make the component a real form control. */\r\nexport type ButtonType = 'button' | 'submit' | 'reset';\r\n\r\n/**\r\n * A themeable button that renders a native `<button>` element, so it is fully\r\n * form-submittable and keyboard/screen-reader accessible out of the box.\r\n */\r\n@Component({\r\n selector: 'compsych-button',\r\n standalone: true,\r\n template: `\r\n <button\r\n [class]=\"buttonClasses\"\r\n [type]=\"type\"\r\n [disabled]=\"disabled || loading\"\r\n [attr.aria-busy]=\"loading ? 'true' : null\"\r\n [attr.aria-label]=\"computedAriaLabel\"\r\n >\r\n @if (icon && (iconOnly || iconPosition === 'leading')) {\r\n <span class=\"icon material-symbols-rounded\" aria-hidden=\"true\">{{ icon }}</span>\r\n }\r\n @if (!iconOnly) {\r\n <span class=\"label\">{{ label }}</span>\r\n }\r\n @if (icon && !iconOnly && iconPosition === 'trailing') {\r\n <span class=\"icon material-symbols-rounded\" aria-hidden=\"true\">{{ icon }}</span>\r\n }\r\n @if (loading) {\r\n <span class=\"spinner-overlay\" aria-hidden=\"true\">\r\n <span class=\"icon spinner material-symbols-rounded\">progress_activity</span>\r\n </span>\r\n }\r\n </button>\r\n `,\r\n styleUrl: './button.css',\r\n})\r\nexport class ButtonComponent {\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: ButtonSize = 'small';\r\n /** Material Symbols icon name (e.g. `'search'`, `'arrow_forward'`). Omit to show label only. */\r\n @Input() icon?: string;\r\n /** Placement of `icon` relative to the label. Defaults to `'leading'`. */\r\n @Input() iconPosition: ButtonIconPosition = 'leading';\r\n /** Native button type. Use `'submit'` inside a `<form>` to submit it. Defaults to `'button'`. */\r\n @Input() type: ButtonType = 'button';\r\n /**\r\n * Accessible name. Required for icon-only buttons since the icon is decorative\r\n * (`aria-hidden`). When `iconOnly` is set and this is omitted, `label` is used.\r\n */\r\n @Input() ariaLabel?: string;\r\n\r\n /** Hides the label and renders a square icon-only button. Requires `icon`. Supports boolean coercion. */\r\n @Input({ transform: booleanAttribute }) iconOnly = false;\r\n /** Disables the button — non-interactive and dimmed. Supports boolean coercion. */\r\n @Input({ transform: booleanAttribute }) disabled = false;\r\n /**\r\n * Loading state. Replaces the content with a centered spinner while preserving\r\n * the button's width, blocks re-clicks, and exposes `aria-busy=\"true\"`. The\r\n * variant's colours are retained (unlike `disabled`, which dims the button).\r\n * Supports boolean coercion.\r\n */\r\n @Input({ transform: booleanAttribute }) loading = false;\r\n\r\n /** Space-separated class list applied to the inner native button. */\r\n get buttonClasses(): string {\r\n return [\r\n 'cmp-btn',\r\n this.variant,\r\n this.size,\r\n this.iconOnly ? 'icon-only' : '',\r\n this.loading ? 'loading' : '',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n }\r\n\r\n /** Resolves the accessible name, guaranteeing icon-only and loading buttons are labelled. */\r\n get computedAriaLabel(): string | null {\r\n if (this.ariaLabel) {\r\n return this.ariaLabel;\r\n }\r\n // Icon-only buttons have no visible text; while loading the label is hidden from the\r\n // a11y tree. In both cases fall back to `label` so the button keeps an accessible name.\r\n return this.iconOnly || this.loading ? this.label || null : null;\r\n }\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"button.js","sourceRoot":"","sources":["../../../../../../packages/angular/src/lib/button/button.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n HostBinding,\n Input,\n OnChanges,\n booleanAttribute,\n inject,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { LUCIDE_ICONS } from '@compsych-ui-components/shared/assets/lucide-icons.generated';\n\n/** Visual style of a `compsych-button`. Controls color, border, and elevation. */\nexport type ButtonVariant =\n | 'filled'\n | 'tonal'\n | 'outlined'\n | 'elevated'\n | 'text'\n | 'danger'\n | 'danger-outlined';\n\n/** Button dimensions — controls height, padding, font size, and icon size. */\nexport type ButtonSize = 'small' | 'medium' | 'large' | 'xlarge';\n\n/** Native button behaviour. `'submit'`/`'reset'` make the component a real form control. */\nexport type ButtonType = 'button' | 'submit' | 'reset';\n\n/** Icon pixel size per button size (per design: sm/md 16, lg 20, xl 24). */\nconst ICON_SIZE_BY_BUTTON_SIZE: Record<ButtonSize, number> = {\n small: 16,\n medium: 16,\n large: 20,\n xlarge: 24,\n};\n\n/**\n * A themeable button that renders a native `<button>` element, so it is fully\n * form-submittable and keyboard/screen-reader accessible out of the box. Icons\n * are Lucide glyphs (matching the rest of the library); a button may carry a\n * leading icon, a trailing icon, or both.\n */\n@Component({\n selector: 'compsych-button',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <button\n [class]=\"buttonClasses\"\n [type]=\"type\"\n [disabled]=\"disabled || loading\"\n [attr.aria-busy]=\"loading ? 'true' : null\"\n [attr.aria-label]=\"computedAriaLabel\"\n (pointerdown)=\"onPointerDown($event)\"\n (keydown)=\"onKeyDown($event)\"\n >\n @if (loading) {\n <span class=\"icon spinner\" aria-hidden=\"true\"></span>\n } @else if (leadingIcon) {\n <span class=\"icon\" aria-hidden=\"true\" [innerHTML]=\"leadingIconSvg\"></span>\n }\n @if (!iconOnly) {\n <span class=\"label\">{{ label }}<ng-content /></span>\n }\n @if (trailingIcon && !iconOnly) {\n <span class=\"icon\" aria-hidden=\"true\" [innerHTML]=\"trailingIconSvg\"></span>\n }\n </button>\n `,\n styleUrl: './button.css',\n})\nexport class ButtonComponent implements OnChanges {\n private readonly sanitizer = inject(DomSanitizer);\n\n /** Text label rendered on the button. */\n @Input() label = '';\n /** Visual style of the button. Defaults to `'filled'`. */\n @Input() variant: ButtonVariant = 'filled';\n /** Button dimensions — controls height, padding, font size, and icon size. Defaults to `'small'`. */\n @Input() size: ButtonSize = 'small';\n /** Lucide icon name shown before the label (e.g. `'house'`). Swapped for a spinner when loading. */\n @Input() leadingIcon?: string;\n /** Lucide icon name shown after the label (e.g. `'chevron-right'`). Can be combined with `leadingIcon`. */\n @Input() trailingIcon?: string;\n /** Native button type. Use `'submit'` inside a `<form>` to submit it. Defaults to `'button'`. */\n @Input() type: ButtonType = 'button';\n /**\n * Accessible name. Required for icon-only buttons since the icon is decorative.\n * When `iconOnly` or `loading` is set and this is omitted, `label` is used.\n */\n @Input() ariaLabel?: string;\n\n /** Hides the label and renders a square icon-only button. Uses `leadingIcon`. Supports boolean coercion. */\n @Input({ transform: booleanAttribute }) iconOnly = false;\n /** Disables the button — non-interactive and rendered in a neutral grey. Supports boolean coercion. */\n @Input({ transform: booleanAttribute }) disabled = false;\n /**\n * Loading state. Swaps the leading icon for a spinner and suppresses activation\n * (native `disabled` blocks click/keyboard), while keeping the label and exposing\n * `aria-busy=\"true\"`. Retains the variant's colours. Supports boolean coercion.\n */\n @Input({ transform: booleanAttribute }) loading = false;\n /** Stretches the button to the full width of its container. Supports boolean coercion. */\n @Input({ transform: booleanAttribute }) fullWidth = false;\n\n /** Mirrors `fullWidth` onto the host so it can stretch. */\n @HostBinding('class.full-width') get hostFullWidth(): boolean {\n return this.fullWidth;\n }\n\n /** Pre-sanitized icon markup, recomputed on input change (avoids re-sanitizing every CD cycle). */\n protected leadingIconSvg: SafeHtml | null = null;\n protected trailingIconSvg: SafeHtml | null = null;\n\n ngOnChanges(): void {\n this.leadingIconSvg = this.leadingIcon ? this.buildIconSvg(this.leadingIcon) : null;\n this.trailingIconSvg = this.trailingIcon ? this.buildIconSvg(this.trailingIcon) : null;\n }\n\n /** Space-separated class list applied to the inner native button. */\n get buttonClasses(): string {\n return [\n 'cmp-btn',\n this.variant,\n this.size,\n this.iconOnly ? 'icon-only' : '',\n this.loading ? 'loading' : '',\n this.fullWidth ? 'full-width' : '',\n ]\n .filter(Boolean)\n .join(' ');\n }\n\n /** Resolves the accessible name. Icon-only buttons have no visible text, so they fall back to `label`. */\n get computedAriaLabel(): string | null {\n if (this.ariaLabel) {\n return this.ariaLabel;\n }\n // Loading keeps the visible label, so only icon-only needs an explicit name here.\n return this.iconOnly ? this.label || null : null;\n }\n\n /**\n * Looks up a Lucide icon by name and injects an explicit width/height so the SVG\n * renders at the correct size when injected via `[innerHTML]` (Angular's CSS\n * encapsulation does not reach dynamically injected content).\n */\n private buildIconSvg(name: string): SafeHtml {\n const size = ICON_SIZE_BY_BUTTON_SIZE[this.size];\n const svg = (LUCIDE_ICONS[name] ?? '').replace('<svg ', `<svg width=\"${size}\" height=\"${size}\" `);\n return this.sanitizer.bypassSecurityTrustHtml(svg);\n }\n\n /** M3 pressed ripple: pointer-down spawns a ripple at the pointer position. */\n onPointerDown(event: PointerEvent): void {\n if (this.disabled || this.loading || event.button !== 0) {\n return;\n }\n const host = event.currentTarget as HTMLElement;\n const rect = host.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n this.spawnRipple(host, x, y, this.farthestCornerDiameter(rect, x, y));\n }\n\n /** M3 pressed ripple: keyboard activation (Enter/Space) spawns a centered ripple. */\n onKeyDown(event: KeyboardEvent): void {\n if (this.disabled || this.loading || event.repeat) {\n return;\n }\n if (event.key !== 'Enter' && event.key !== ' ') {\n return;\n }\n const host = event.currentTarget as HTMLElement;\n const rect = host.getBoundingClientRect();\n this.spawnRipple(host, rect.width / 2, rect.height / 2, Math.hypot(rect.width, rect.height));\n }\n\n /** Diameter that reaches the farthest corner from (x, y), so the ripple always covers the button. */\n private farthestCornerDiameter(rect: DOMRect, x: number, y: number): number {\n const corners: Array<[number, number]> = [\n [0, 0],\n [rect.width, 0],\n [0, rect.height],\n [rect.width, rect.height],\n ];\n const maxRadius = Math.max(...corners.map(([cx, cy]) => Math.hypot(cx - x, cy - y)));\n return maxRadius * 2;\n }\n\n /**\n * Spawns an expanding ripple circle. Created imperatively with inline styles and\n * animated via the Web Animations API — Angular's emulated encapsulation does not\n * reach dynamically created nodes, so it cannot rely on the component stylesheet.\n */\n private spawnRipple(host: HTMLElement, x: number, y: number, diameter: number): void {\n if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {\n return;\n }\n const ripple = document.createElement('span');\n Object.assign(ripple.style, {\n position: 'absolute',\n borderRadius: '50%',\n backgroundColor: 'currentColor',\n pointerEvents: 'none',\n width: `${diameter}px`,\n height: `${diameter}px`,\n left: `${x - diameter / 2}px`,\n top: `${y - diameter / 2}px`,\n } as Partial<CSSStyleDeclaration>);\n host.appendChild(ripple);\n const animation = ripple.animate(\n [\n { transform: 'scale(0)', opacity: '0.16' },\n { transform: 'scale(1)', opacity: '0' },\n ],\n { duration: 450, easing: 'ease-out' },\n );\n animation.onfinish = () => ripple.remove();\n }\n}\n"]}
|