@fuse_ui/tabs 0.0.1

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/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # tabs
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Running unit tests
6
+
7
+ Run `nx test tabs` to execute the unit tests.
@@ -0,0 +1,100 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, input, inject, computed, ChangeDetectionStrategy, Component, output, contentChildren, viewChild, viewChildren, signal, Injector, DestroyRef, effect, afterNextRender } from '@angular/core';
3
+ import { FuseBadgeComponent } from '@fuse_ui/badge';
4
+
5
+ const FUSE_TABS = new InjectionToken('FUSE_TABS');
6
+
7
+ class FuseTabComponent {
8
+ // ─── Inputs ──────────────────────────────────────────────────────────────────
9
+ tabId = input.required(...(ngDevMode ? [{ debugName: "tabId" }] : /* istanbul ignore next */ []));
10
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
11
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
12
+ badge = input(null, ...(ngDevMode ? [{ debugName: "badge" }] : /* istanbul ignore next */ []));
13
+ // ─── Parent context ───────────────────────────────────────────────────────────
14
+ tabs = inject(FUSE_TABS);
15
+ // ─── Derived ─────────────────────────────────────────────────────────────────
16
+ isActive = computed(() => this.tabs.activeTabId() === this.tabId(), ...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
17
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.6", type: FuseTabComponent, isStandalone: true, selector: "fuse-tab", inputs: { tabId: { classPropertyName: "tabId", publicName: "tabId", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "fuse-tab-host" }, ngImport: i0, template: "<div\n role=\"tabpanel\"\n [id]=\"'panel-' + tabId()\"\n [attr.aria-labelledby]=\"tabId()\"\n [hidden]=\"!isActive()\"\n class=\"fuse-tab__panel\">\n <ng-content></ng-content>\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
19
+ }
20
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseTabComponent, decorators: [{
21
+ type: Component,
22
+ args: [{ selector: 'fuse-tab', standalone: true, imports: [FuseBadgeComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fuse-tab-host' }, template: "<div\n role=\"tabpanel\"\n [id]=\"'panel-' + tabId()\"\n [attr.aria-labelledby]=\"tabId()\"\n [hidden]=\"!isActive()\"\n class=\"fuse-tab__panel\">\n <ng-content></ng-content>\n</div>\n" }]
23
+ }], propDecorators: { tabId: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabId", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }] } });
24
+
25
+ class FuseTabsComponent {
26
+ // ─── Inputs ──────────────────────────────────────────────────────────────────
27
+ activeTab = input('', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
28
+ variant = input('line', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
29
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
30
+ // ─── Outputs ─────────────────────────────────────────────────────────────────
31
+ tabChange = output();
32
+ // ─── Queries ─────────────────────────────────────────────────────────────────
33
+ tabs = contentChildren(FuseTabComponent, ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
34
+ tabList = viewChild.required('tabList');
35
+ indicator = viewChild('indicator', ...(ngDevMode ? [{ debugName: "indicator" }] : /* istanbul ignore next */ []));
36
+ tabButtons = viewChildren('tabBtn', ...(ngDevMode ? [{ debugName: "tabButtons" }] : /* istanbul ignore next */ []));
37
+ // ─── State ───────────────────────────────────────────────────────────────────
38
+ activeTabId = signal('', ...(ngDevMode ? [{ debugName: "activeTabId" }] : /* istanbul ignore next */ []));
39
+ // ─── Services ────────────────────────────────────────────────────────────────
40
+ injector = inject(Injector);
41
+ destroyRef = inject(DestroyRef);
42
+ // ─── Computed ────────────────────────────────────────────────────────────────
43
+ tabListClass = computed(() => [
44
+ 'fuse-tabs__list',
45
+ `fuse-tabs__list--${this.variant()}`,
46
+ `fuse-tabs__list--${this.size()}`,
47
+ ].join(' '), ...(ngDevMode ? [{ debugName: "tabListClass" }] : /* istanbul ignore next */ []));
48
+ tabBtnClass = (tab) => computed(() => [
49
+ 'fuse-tab-btn',
50
+ this.activeTabId() === tab.tabId() ? 'fuse-tab-btn--active' : '',
51
+ tab.disabled() ? 'fuse-tab-btn--disabled' : '',
52
+ ].filter(Boolean).join(' '));
53
+ // ─── Sync activeTab input → activeTabId signal ───────────────────────────────
54
+ _sync = effect(() => {
55
+ const desired = this.activeTab() || this.tabs()[0]?.tabId() || '';
56
+ this.activeTabId.set(desired);
57
+ }, ...(ngDevMode ? [{ debugName: "_sync" }] : /* istanbul ignore next */ []));
58
+ // ─── Lifecycle ───────────────────────────────────────────────────────────────
59
+ constructor() {
60
+ afterNextRender(() => this.updateIndicator());
61
+ }
62
+ ngAfterViewInit() {
63
+ // Re-measure when tab list is ready (handles SSR-safe fallback)
64
+ this.updateIndicator();
65
+ }
66
+ // ─── Public API (TabsRef) ────────────────────────────────────────────────────
67
+ setActive(tabId) {
68
+ this.activeTabId.set(tabId);
69
+ this.tabChange.emit(tabId);
70
+ // Wait for Angular to re-render the active class before measuring
71
+ afterNextRender(() => this.updateIndicator(), { injector: this.injector });
72
+ }
73
+ // ─── Indicator ───────────────────────────────────────────────────────────────
74
+ updateIndicator() {
75
+ const indicatorRef = this.indicator();
76
+ if (!indicatorRef)
77
+ return; // only 'line' variant has indicator
78
+ const buttons = this.tabButtons();
79
+ const idx = this.tabs().findIndex(t => t.tabId() === this.activeTabId());
80
+ if (idx < 0 || !buttons[idx])
81
+ return;
82
+ const btn = buttons[idx].nativeElement;
83
+ const el = indicatorRef.nativeElement;
84
+ el.style.left = `${btn.offsetLeft}px`;
85
+ el.style.width = `${btn.offsetWidth}px`;
86
+ }
87
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
88
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuseTabsComponent, isStandalone: true, selector: "fuse-tabs", inputs: { activeTab: { classPropertyName: "activeTab", publicName: "activeTab", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tabChange: "tabChange" }, host: { classAttribute: "fuse-tabs-host" }, providers: [{ provide: FUSE_TABS, useExisting: FuseTabsComponent }], queries: [{ propertyName: "tabs", predicate: FuseTabComponent, isSignal: true }], viewQueries: [{ propertyName: "tabList", first: true, predicate: ["tabList"], descendants: true, isSignal: true }, { propertyName: "indicator", first: true, predicate: ["indicator"], descendants: true, isSignal: true }, { propertyName: "tabButtons", predicate: ["tabBtn"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #tabList [class]=\"tabListClass()\" role=\"tablist\">\n @for (tab of tabs(); track tab.tabId()) {\n <button\n #tabBtn\n role=\"tab\"\n type=\"button\"\n [id]=\"tab.tabId()\"\n [attr.aria-selected]=\"activeTabId() === tab.tabId()\"\n [attr.aria-controls]=\"'panel-' + tab.tabId()\"\n [disabled]=\"tab.disabled() || null\"\n [class]=\"tabBtnClass(tab)()\"\n (click)=\"setActive(tab.tabId())\">\n {{ tab.label() }}\n @if (tab.badge()) {\n <fuse-badge [content]=\"tab.badge()!\" size=\"sm\"></fuse-badge>\n }\n </button>\n }\n\n @if (variant() === 'line') {\n <div #indicator class=\"fuse-tabs__indicator\" aria-hidden=\"true\"></div>\n }\n</div>\n\n<div class=\"fuse-tabs__panels\">\n <ng-content></ng-content>\n</div>\n", styles: [":host{display:block;font-family:var(--fuse-font-family, system-ui, sans-serif)}:host-context(.ios){--fuse-tabs-radius: var(--fuse-radius-lg, 12px)}:host-context(.md){--fuse-tabs-radius: var(--fuse-radius-md, 8px)}.fuse-tabs__list{position:relative;display:flex;align-items:center;gap:0}.fuse-tabs__list--line{border-bottom:1px solid var(--fuse-color-border-default);gap:0}.fuse-tabs__list--pills{gap:var(--fuse-spacing-1, 4px);padding:var(--fuse-spacing-1, 4px)}.fuse-tabs__list--boxed{gap:0;background:var(--fuse-color-bg-sunken);border:1px solid var(--fuse-color-border-default);border-radius:var(--fuse-tabs-radius, var(--fuse-radius-md, 8px));padding:var(--fuse-spacing-1, 4px)}.fuse-tab-btn{display:inline-flex;align-items:center;gap:var(--fuse-spacing-1, 6px);border:none;background:transparent;color:var(--fuse-color-text-secondary);font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;position:relative;transition:color var(--fuse-duration-fast, .15s) var(--fuse-easing-smooth, ease),background var(--fuse-duration-fast, .15s) var(--fuse-easing-smooth, ease)}@media(prefers-reduced-motion:reduce){.fuse-tab-btn{transition:none}}.fuse-tab-btn:focus-visible{outline:2px solid var(--fuse-color-border-focus);outline-offset:2px;border-radius:var(--fuse-radius-sm, 4px)}.fuse-tab-btn:hover:not(.fuse-tab-btn--disabled):not(.fuse-tab-btn--active){color:var(--fuse-color-text-primary)}.fuse-tab-btn.fuse-tab-btn--active{color:var(--fuse-color-primary)}.fuse-tab-btn.fuse-tab-btn--disabled,.fuse-tab-btn:disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.fuse-tabs__list--sm .fuse-tab-btn{padding:var(--fuse-spacing-2, 6px) var(--fuse-spacing-3, 10px);font-size:var(--fuse-fluid-sm, .875rem)}.fuse-tabs__list--md .fuse-tab-btn{padding:var(--fuse-spacing-3, 8px) var(--fuse-spacing-4, 14px);font-size:var(--fuse-fluid-md, 1rem)}.fuse-tabs__list--pills .fuse-tab-btn{border-radius:var(--fuse-radius-full, 9999px)}.fuse-tabs__list--pills .fuse-tab-btn.fuse-tab-btn--active{background:var(--fuse-color-primary);color:var(--fuse-color-on-primary)}.fuse-tabs__list--boxed .fuse-tab-btn{border-radius:calc(var(--fuse-tabs-radius, var(--fuse-radius-md, 8px)) - 2px);flex:1;justify-content:center}.fuse-tabs__list--boxed .fuse-tab-btn.fuse-tab-btn--active{background:var(--fuse-color-bg-surface);color:var(--fuse-color-text-primary);box-shadow:var(--fuse-shadow-sm)}.fuse-tabs__indicator{position:absolute;bottom:-1px;height:2px;background:var(--fuse-color-primary);border-radius:var(--fuse-radius-full, 9999px);transition:left var(--fuse-duration-slow, .3s) var(--fuse-easing-spring-enter, cubic-bezier(.34, 1.56, .64, 1)),width var(--fuse-duration-slow, .3s) var(--fuse-easing-spring-enter, cubic-bezier(.34, 1.56, .64, 1))}@media(prefers-reduced-motion:reduce){.fuse-tabs__indicator{transition:none}}.fuse-tabs__panels{padding-top:var(--fuse-spacing-4, 16px)}fuse-tab{display:block}\n"], dependencies: [{ kind: "component", type: FuseBadgeComponent, selector: "fuse-badge", inputs: ["variant", "color", "size", "dot", "content"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
89
+ }
90
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseTabsComponent, decorators: [{
91
+ type: Component,
92
+ args: [{ selector: 'fuse-tabs', standalone: true, imports: [FuseTabComponent, FuseBadgeComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: FUSE_TABS, useExisting: FuseTabsComponent }], host: { class: 'fuse-tabs-host' }, template: "<div #tabList [class]=\"tabListClass()\" role=\"tablist\">\n @for (tab of tabs(); track tab.tabId()) {\n <button\n #tabBtn\n role=\"tab\"\n type=\"button\"\n [id]=\"tab.tabId()\"\n [attr.aria-selected]=\"activeTabId() === tab.tabId()\"\n [attr.aria-controls]=\"'panel-' + tab.tabId()\"\n [disabled]=\"tab.disabled() || null\"\n [class]=\"tabBtnClass(tab)()\"\n (click)=\"setActive(tab.tabId())\">\n {{ tab.label() }}\n @if (tab.badge()) {\n <fuse-badge [content]=\"tab.badge()!\" size=\"sm\"></fuse-badge>\n }\n </button>\n }\n\n @if (variant() === 'line') {\n <div #indicator class=\"fuse-tabs__indicator\" aria-hidden=\"true\"></div>\n }\n</div>\n\n<div class=\"fuse-tabs__panels\">\n <ng-content></ng-content>\n</div>\n", styles: [":host{display:block;font-family:var(--fuse-font-family, system-ui, sans-serif)}:host-context(.ios){--fuse-tabs-radius: var(--fuse-radius-lg, 12px)}:host-context(.md){--fuse-tabs-radius: var(--fuse-radius-md, 8px)}.fuse-tabs__list{position:relative;display:flex;align-items:center;gap:0}.fuse-tabs__list--line{border-bottom:1px solid var(--fuse-color-border-default);gap:0}.fuse-tabs__list--pills{gap:var(--fuse-spacing-1, 4px);padding:var(--fuse-spacing-1, 4px)}.fuse-tabs__list--boxed{gap:0;background:var(--fuse-color-bg-sunken);border:1px solid var(--fuse-color-border-default);border-radius:var(--fuse-tabs-radius, var(--fuse-radius-md, 8px));padding:var(--fuse-spacing-1, 4px)}.fuse-tab-btn{display:inline-flex;align-items:center;gap:var(--fuse-spacing-1, 6px);border:none;background:transparent;color:var(--fuse-color-text-secondary);font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;position:relative;transition:color var(--fuse-duration-fast, .15s) var(--fuse-easing-smooth, ease),background var(--fuse-duration-fast, .15s) var(--fuse-easing-smooth, ease)}@media(prefers-reduced-motion:reduce){.fuse-tab-btn{transition:none}}.fuse-tab-btn:focus-visible{outline:2px solid var(--fuse-color-border-focus);outline-offset:2px;border-radius:var(--fuse-radius-sm, 4px)}.fuse-tab-btn:hover:not(.fuse-tab-btn--disabled):not(.fuse-tab-btn--active){color:var(--fuse-color-text-primary)}.fuse-tab-btn.fuse-tab-btn--active{color:var(--fuse-color-primary)}.fuse-tab-btn.fuse-tab-btn--disabled,.fuse-tab-btn:disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.fuse-tabs__list--sm .fuse-tab-btn{padding:var(--fuse-spacing-2, 6px) var(--fuse-spacing-3, 10px);font-size:var(--fuse-fluid-sm, .875rem)}.fuse-tabs__list--md .fuse-tab-btn{padding:var(--fuse-spacing-3, 8px) var(--fuse-spacing-4, 14px);font-size:var(--fuse-fluid-md, 1rem)}.fuse-tabs__list--pills .fuse-tab-btn{border-radius:var(--fuse-radius-full, 9999px)}.fuse-tabs__list--pills .fuse-tab-btn.fuse-tab-btn--active{background:var(--fuse-color-primary);color:var(--fuse-color-on-primary)}.fuse-tabs__list--boxed .fuse-tab-btn{border-radius:calc(var(--fuse-tabs-radius, var(--fuse-radius-md, 8px)) - 2px);flex:1;justify-content:center}.fuse-tabs__list--boxed .fuse-tab-btn.fuse-tab-btn--active{background:var(--fuse-color-bg-surface);color:var(--fuse-color-text-primary);box-shadow:var(--fuse-shadow-sm)}.fuse-tabs__indicator{position:absolute;bottom:-1px;height:2px;background:var(--fuse-color-primary);border-radius:var(--fuse-radius-full, 9999px);transition:left var(--fuse-duration-slow, .3s) var(--fuse-easing-spring-enter, cubic-bezier(.34, 1.56, .64, 1)),width var(--fuse-duration-slow, .3s) var(--fuse-easing-spring-enter, cubic-bezier(.34, 1.56, .64, 1))}@media(prefers-reduced-motion:reduce){.fuse-tabs__indicator{transition:none}}.fuse-tabs__panels{padding-top:var(--fuse-spacing-4, 16px)}fuse-tab{display:block}\n"] }]
93
+ }], ctorParameters: () => [], propDecorators: { activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }], tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FuseTabComponent), { isSignal: true }] }], tabList: [{ type: i0.ViewChild, args: ['tabList', { isSignal: true }] }], indicator: [{ type: i0.ViewChild, args: ['indicator', { isSignal: true }] }], tabButtons: [{ type: i0.ViewChildren, args: ['tabBtn', { isSignal: true }] }] } });
94
+
95
+ /**
96
+ * Generated bundle index. Do not edit.
97
+ */
98
+
99
+ export { FUSE_TABS, FuseTabComponent, FuseTabsComponent };
100
+ //# sourceMappingURL=fuse_ui-tabs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fuse_ui-tabs.mjs","sources":["../../../../packages/tabs/src/lib/tabs/fuse-tabs.token.ts","../../../../packages/tabs/src/lib/tabs/fuse-tab.component.ts","../../../../packages/tabs/src/lib/tabs/fuse-tab.component.html","../../../../packages/tabs/src/lib/tabs/fuse-tabs.component.ts","../../../../packages/tabs/src/lib/tabs/fuse-tabs.component.html","../../../../packages/tabs/src/fuse_ui-tabs.ts"],"sourcesContent":["import { InjectionToken, Signal } from '@angular/core';\n\nexport interface TabsRef {\n activeTabId: Signal<string>;\n setActive(tabId: string): void;\n}\n\nexport const FUSE_TABS = new InjectionToken<TabsRef>('FUSE_TABS');\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n input,\n} from '@angular/core';\nimport { FuseBadgeComponent } from '@fuse_ui/badge';\nimport { FUSE_TABS } from './fuse-tabs.token';\n\n@Component({\n selector: 'fuse-tab',\n standalone: true,\n imports: [FuseBadgeComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './fuse-tab.component.html',\n host: { class: 'fuse-tab-host' },\n})\nexport class FuseTabComponent {\n // ─── Inputs ──────────────────────────────────────────────────────────────────\n\n readonly tabId = input.required<string>();\n readonly label = input.required<string>();\n readonly disabled = input(false);\n readonly badge = input<string | null>(null);\n\n // ─── Parent context ───────────────────────────────────────────────────────────\n\n protected readonly tabs = inject(FUSE_TABS);\n\n // ─── Derived ─────────────────────────────────────────────────────────────────\n\n protected readonly isActive = computed(() =>\n this.tabs.activeTabId() === this.tabId()\n );\n}\n","<div\n role=\"tabpanel\"\n [id]=\"'panel-' + tabId()\"\n [attr.aria-labelledby]=\"tabId()\"\n [hidden]=\"!isActive()\"\n class=\"fuse-tab__panel\">\n <ng-content></ng-content>\n</div>\n","import {\n AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n Injector,\n afterNextRender,\n computed,\n contentChildren,\n effect,\n inject,\n output,\n input,\n signal,\n viewChild,\n viewChildren,\n} from '@angular/core';\nimport { FuseBadgeComponent } from '@fuse_ui/badge';\nimport { FuseTabComponent } from './fuse-tab.component';\nimport { FUSE_TABS, TabsRef } from './fuse-tabs.token';\n\n@Component({\n selector: 'fuse-tabs',\n standalone: true,\n imports: [FuseTabComponent, FuseBadgeComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [{ provide: FUSE_TABS, useExisting: FuseTabsComponent }],\n templateUrl: './fuse-tabs.component.html',\n styleUrl: './fuse-tabs.component.scss',\n host: { class: 'fuse-tabs-host' },\n})\nexport class FuseTabsComponent implements TabsRef, AfterViewInit {\n // ─── Inputs ──────────────────────────────────────────────────────────────────\n\n readonly activeTab = input<string>('');\n readonly variant = input<'line' | 'pills' | 'boxed'>('line');\n readonly size = input<'sm' | 'md'>('md');\n\n // ─── Outputs ─────────────────────────────────────────────────────────────────\n\n readonly tabChange = output<string>();\n\n // ─── Queries ─────────────────────────────────────────────────────────────────\n\n readonly tabs = contentChildren(FuseTabComponent);\n readonly tabList = viewChild.required<ElementRef<HTMLElement>>('tabList');\n readonly indicator = viewChild<ElementRef<HTMLElement>>('indicator');\n readonly tabButtons = viewChildren<ElementRef<HTMLButtonElement>>('tabBtn');\n\n // ─── State ───────────────────────────────────────────────────────────────────\n\n readonly activeTabId = signal('');\n\n // ─── Services ────────────────────────────────────────────────────────────────\n\n private readonly injector = inject(Injector);\n private readonly destroyRef = inject(DestroyRef);\n\n // ─── Computed ────────────────────────────────────────────────────────────────\n\n protected readonly tabListClass = computed(() =>\n [\n 'fuse-tabs__list',\n `fuse-tabs__list--${this.variant()}`,\n `fuse-tabs__list--${this.size()}`,\n ].join(' ')\n );\n\n protected readonly tabBtnClass = (tab: FuseTabComponent) =>\n computed(() => [\n 'fuse-tab-btn',\n this.activeTabId() === tab.tabId() ? 'fuse-tab-btn--active' : '',\n tab.disabled() ? 'fuse-tab-btn--disabled' : '',\n ].filter(Boolean).join(' '));\n\n // ─── Sync activeTab input → activeTabId signal ───────────────────────────────\n\n private readonly _sync = effect(() => {\n const desired = this.activeTab() || this.tabs()[0]?.tabId() || '';\n this.activeTabId.set(desired);\n });\n\n // ─── Lifecycle ───────────────────────────────────────────────────────────────\n\n constructor() {\n afterNextRender(() => this.updateIndicator());\n }\n\n ngAfterViewInit(): void {\n // Re-measure when tab list is ready (handles SSR-safe fallback)\n this.updateIndicator();\n }\n\n // ─── Public API (TabsRef) ────────────────────────────────────────────────────\n\n setActive(tabId: string): void {\n this.activeTabId.set(tabId);\n this.tabChange.emit(tabId);\n // Wait for Angular to re-render the active class before measuring\n afterNextRender(() => this.updateIndicator(), { injector: this.injector });\n }\n\n // ─── Indicator ───────────────────────────────────────────────────────────────\n\n private updateIndicator(): void {\n const indicatorRef = this.indicator();\n if (!indicatorRef) return; // only 'line' variant has indicator\n\n const buttons = this.tabButtons();\n const idx = this.tabs().findIndex(t => t.tabId() === this.activeTabId());\n if (idx < 0 || !buttons[idx]) return;\n\n const btn = buttons[idx].nativeElement;\n const el = indicatorRef.nativeElement;\n el.style.left = `${btn.offsetLeft}px`;\n el.style.width = `${btn.offsetWidth}px`;\n }\n}\n","<div #tabList [class]=\"tabListClass()\" role=\"tablist\">\n @for (tab of tabs(); track tab.tabId()) {\n <button\n #tabBtn\n role=\"tab\"\n type=\"button\"\n [id]=\"tab.tabId()\"\n [attr.aria-selected]=\"activeTabId() === tab.tabId()\"\n [attr.aria-controls]=\"'panel-' + tab.tabId()\"\n [disabled]=\"tab.disabled() || null\"\n [class]=\"tabBtnClass(tab)()\"\n (click)=\"setActive(tab.tabId())\">\n {{ tab.label() }}\n @if (tab.badge()) {\n <fuse-badge [content]=\"tab.badge()!\" size=\"sm\"></fuse-badge>\n }\n </button>\n }\n\n @if (variant() === 'line') {\n <div #indicator class=\"fuse-tabs__indicator\" aria-hidden=\"true\"></div>\n }\n</div>\n\n<div class=\"fuse-tabs__panels\">\n <ng-content></ng-content>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@fuse_ui/tabs",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "keywords": [
9
+ "fuse-ui",
10
+ "angular",
11
+ "ionic",
12
+ "ionic8",
13
+ "angular18",
14
+ "angular19",
15
+ "angular20",
16
+ "angular21",
17
+ "ui-components",
18
+ "design-system",
19
+ "css-variables",
20
+ "signals",
21
+ "standalone",
22
+ "multi-theme",
23
+ "dark-mode",
24
+ "fluid-typography",
25
+ "animated"
26
+ ],
27
+ "peerDependencies": {
28
+ "@angular/core": ">=18.0.0",
29
+ "@angular/common": ">=18.0.0",
30
+ "rxjs": ">=7.4.0",
31
+ "@fuse_ui/badge": "0.0.1"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "@ionic/angular": {
35
+ "optional": true
36
+ }
37
+ },
38
+ "sideEffects": [
39
+ "*.css",
40
+ "**/*.scss"
41
+ ],
42
+ "engines": {
43
+ "node": ">=20.0.0"
44
+ },
45
+ "module": "fesm2022/fuse_ui-tabs.mjs",
46
+ "typings": "types/fuse_ui-tabs.d.ts",
47
+ "exports": {
48
+ "./package.json": {
49
+ "default": "./package.json"
50
+ },
51
+ ".": {
52
+ "types": "./types/fuse_ui-tabs.d.ts",
53
+ "default": "./fesm2022/fuse_ui-tabs.mjs"
54
+ }
55
+ },
56
+ "type": "module",
57
+ "dependencies": {
58
+ "tslib": "^2.3.0"
59
+ }
60
+ }
@@ -0,0 +1,46 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { InjectionToken, Signal, AfterViewInit, ElementRef } from '@angular/core';
3
+ import * as _fuse_ui_tabs from '@fuse_ui/tabs';
4
+
5
+ interface TabsRef {
6
+ activeTabId: Signal<string>;
7
+ setActive(tabId: string): void;
8
+ }
9
+ declare const FUSE_TABS: InjectionToken<TabsRef>;
10
+
11
+ declare class FuseTabComponent {
12
+ readonly tabId: _angular_core.InputSignal<string>;
13
+ readonly label: _angular_core.InputSignal<string>;
14
+ readonly disabled: _angular_core.InputSignal<boolean>;
15
+ readonly badge: _angular_core.InputSignal<string | null>;
16
+ protected readonly tabs: _fuse_ui_tabs.TabsRef;
17
+ protected readonly isActive: _angular_core.Signal<boolean>;
18
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FuseTabComponent, never>;
19
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FuseTabComponent, "fuse-tab", never, { "tabId": { "alias": "tabId"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
20
+ }
21
+
22
+ declare class FuseTabsComponent implements TabsRef, AfterViewInit {
23
+ readonly activeTab: _angular_core.InputSignal<string>;
24
+ readonly variant: _angular_core.InputSignal<"line" | "pills" | "boxed">;
25
+ readonly size: _angular_core.InputSignal<"sm" | "md">;
26
+ readonly tabChange: _angular_core.OutputEmitterRef<string>;
27
+ readonly tabs: _angular_core.Signal<readonly FuseTabComponent[]>;
28
+ readonly tabList: _angular_core.Signal<ElementRef<HTMLElement>>;
29
+ readonly indicator: _angular_core.Signal<ElementRef<HTMLElement> | undefined>;
30
+ readonly tabButtons: _angular_core.Signal<readonly ElementRef<HTMLButtonElement>[]>;
31
+ readonly activeTabId: _angular_core.WritableSignal<string>;
32
+ private readonly injector;
33
+ private readonly destroyRef;
34
+ protected readonly tabListClass: _angular_core.Signal<string>;
35
+ protected readonly tabBtnClass: (tab: FuseTabComponent) => _angular_core.Signal<string>;
36
+ private readonly _sync;
37
+ constructor();
38
+ ngAfterViewInit(): void;
39
+ setActive(tabId: string): void;
40
+ private updateIndicator;
41
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FuseTabsComponent, never>;
42
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FuseTabsComponent, "fuse-tabs", never, { "activeTab": { "alias": "activeTab"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, { "tabChange": "tabChange"; }, ["tabs"], ["*"], true, never>;
43
+ }
44
+
45
+ export { FUSE_TABS, FuseTabComponent, FuseTabsComponent };
46
+ export type { TabsRef };