@kanso-protocol/tabs 0.3.0 → 0.5.2
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.
|
@@ -64,7 +64,7 @@ class KpTabComponent {
|
|
|
64
64
|
<ng-content select="[kpTabBadge]"/>
|
|
65
65
|
</span>
|
|
66
66
|
</span>
|
|
67
|
-
`, isInline: true, styles: [":host{display:inline-flex;box-sizing:border-box;height:var(--kp-tab-h);border-bottom:2px solid var(--kp-color-tabs-tab-underline-rest, transparent);font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif);transition:border-color var(--kp-motion-duration-fast) ease,color .12s ease}:host(.kp-tab--full-width){flex:1 1 0}.kp-tab__btn{all:unset;display:inline-flex;align-items:center;justify-content:center;gap:var(--kp-tab-gap);padding:0 var(--kp-tab-pad-x);height:100%;width:100%;color:var(--kp-color-tabs-tab-fg-rest
|
|
67
|
+
`, isInline: true, styles: [":host{display:inline-flex;box-sizing:border-box;height:var(--kp-tab-h);border-bottom:2px solid var(--kp-color-tabs-tab-underline-rest, transparent);font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif);transition:border-color var(--kp-motion-duration-fast) ease,color .12s ease}:host(.kp-tab--full-width){flex:1 1 0}.kp-tab__btn{all:unset;display:inline-flex;align-items:center;justify-content:center;gap:var(--kp-tab-gap);padding:0 var(--kp-tab-pad-x);height:100%;width:100%;color:var(--kp-color-tabs-tab-fg-rest);cursor:pointer;font-size:var(--kp-tab-font-size);line-height:var(--kp-tab-line-height);font-weight:500}.kp-tab__btn:focus-visible{outline:2px solid var(--kp-color-focus-ring);outline-offset:-2px}.kp-tab__icon{display:inline-flex;align-items:center;color:var(--kp-color-tabs-tab-icon-rest)}.kp-tab__icon:empty{display:none}.kp-tab__icon ::ng-deep svg{width:var(--kp-tab-icon-size);height:var(--kp-tab-icon-size)}.kp-tab__badge{display:inline-flex}.kp-tab__badge:empty{display:none}:host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover){border-bottom-color:var(--kp-color-tabs-tab-underline-hover)}:host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) .kp-tab__btn{color:var(--kp-color-tabs-tab-fg-hover)}:host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) .kp-tab__icon{color:var(--kp-color-tabs-tab-icon-hover)}:host(.kp-tab--selected){border-bottom-color:var(--kp-color-tabs-tab-underline-selected)}:host(.kp-tab--selected) .kp-tab__btn{color:var(--kp-color-tabs-tab-fg-selected)}:host(.kp-tab--selected) .kp-tab__icon{color:var(--kp-color-tabs-tab-icon-selected)}:host(.kp-tab--disabled){border-bottom-color:var(--kp-color-tabs-tab-underline-disabled, transparent)}:host(.kp-tab--disabled) .kp-tab__btn{color:var(--kp-color-tabs-tab-fg-disabled);cursor:not-allowed}:host(.kp-tab--disabled) .kp-tab__icon{color:var(--kp-color-tabs-tab-icon-disabled)}:host(.kp-tab--sm){--kp-tab-h: 32px;--kp-tab-pad-x: 12px;--kp-tab-gap: 6px;--kp-tab-font-size: 14px;--kp-tab-line-height: 20px;--kp-tab-icon-size: 14px}:host(.kp-tab--md){--kp-tab-h: 40px;--kp-tab-pad-x: 16px;--kp-tab-gap: 8px;--kp-tab-font-size: 14px;--kp-tab-line-height: 20px;--kp-tab-icon-size: 16px}:host(.kp-tab--lg){--kp-tab-h: 48px;--kp-tab-pad-x: 20px;--kp-tab-gap: 8px;--kp-tab-font-size: 16px;--kp-tab-line-height: 24px;--kp-tab-icon-size: 16px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
68
68
|
}
|
|
69
69
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpTabComponent, decorators: [{
|
|
70
70
|
type: Component,
|
|
@@ -86,7 +86,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
86
86
|
<ng-content select="[kpTabBadge]"/>
|
|
87
87
|
</span>
|
|
88
88
|
</span>
|
|
89
|
-
`, styles: [":host{display:inline-flex;box-sizing:border-box;height:var(--kp-tab-h);border-bottom:2px solid var(--kp-color-tabs-tab-underline-rest, transparent);font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif);transition:border-color var(--kp-motion-duration-fast) ease,color .12s ease}:host(.kp-tab--full-width){flex:1 1 0}.kp-tab__btn{all:unset;display:inline-flex;align-items:center;justify-content:center;gap:var(--kp-tab-gap);padding:0 var(--kp-tab-pad-x);height:100%;width:100%;color:var(--kp-color-tabs-tab-fg-rest
|
|
89
|
+
`, styles: [":host{display:inline-flex;box-sizing:border-box;height:var(--kp-tab-h);border-bottom:2px solid var(--kp-color-tabs-tab-underline-rest, transparent);font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif);transition:border-color var(--kp-motion-duration-fast) ease,color .12s ease}:host(.kp-tab--full-width){flex:1 1 0}.kp-tab__btn{all:unset;display:inline-flex;align-items:center;justify-content:center;gap:var(--kp-tab-gap);padding:0 var(--kp-tab-pad-x);height:100%;width:100%;color:var(--kp-color-tabs-tab-fg-rest);cursor:pointer;font-size:var(--kp-tab-font-size);line-height:var(--kp-tab-line-height);font-weight:500}.kp-tab__btn:focus-visible{outline:2px solid var(--kp-color-focus-ring);outline-offset:-2px}.kp-tab__icon{display:inline-flex;align-items:center;color:var(--kp-color-tabs-tab-icon-rest)}.kp-tab__icon:empty{display:none}.kp-tab__icon ::ng-deep svg{width:var(--kp-tab-icon-size);height:var(--kp-tab-icon-size)}.kp-tab__badge{display:inline-flex}.kp-tab__badge:empty{display:none}:host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover){border-bottom-color:var(--kp-color-tabs-tab-underline-hover)}:host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) .kp-tab__btn{color:var(--kp-color-tabs-tab-fg-hover)}:host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) .kp-tab__icon{color:var(--kp-color-tabs-tab-icon-hover)}:host(.kp-tab--selected){border-bottom-color:var(--kp-color-tabs-tab-underline-selected)}:host(.kp-tab--selected) .kp-tab__btn{color:var(--kp-color-tabs-tab-fg-selected)}:host(.kp-tab--selected) .kp-tab__icon{color:var(--kp-color-tabs-tab-icon-selected)}:host(.kp-tab--disabled){border-bottom-color:var(--kp-color-tabs-tab-underline-disabled, transparent)}:host(.kp-tab--disabled) .kp-tab__btn{color:var(--kp-color-tabs-tab-fg-disabled);cursor:not-allowed}:host(.kp-tab--disabled) .kp-tab__icon{color:var(--kp-color-tabs-tab-icon-disabled)}:host(.kp-tab--sm){--kp-tab-h: 32px;--kp-tab-pad-x: 12px;--kp-tab-gap: 6px;--kp-tab-font-size: 14px;--kp-tab-line-height: 20px;--kp-tab-icon-size: 14px}:host(.kp-tab--md){--kp-tab-h: 40px;--kp-tab-pad-x: 16px;--kp-tab-gap: 8px;--kp-tab-font-size: 14px;--kp-tab-line-height: 20px;--kp-tab-icon-size: 16px}:host(.kp-tab--lg){--kp-tab-h: 48px;--kp-tab-pad-x: 20px;--kp-tab-gap: 8px;--kp-tab-font-size: 16px;--kp-tab-line-height: 24px;--kp-tab-icon-size: 16px}\n"] }]
|
|
90
90
|
}], propDecorators: { size: [{
|
|
91
91
|
type: Input
|
|
92
92
|
}], label: [{
|
|
@@ -203,7 +203,7 @@ class KpTabsComponent {
|
|
|
203
203
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: KpTabsComponent, isStandalone: true, selector: "kp-tabs", inputs: { size: "size", fullWidth: "fullWidth" }, host: { listeners: { "keydown": "onKeyDown($event)" }, properties: { "class": "hostClasses" } }, queries: [{ propertyName: "tabs", predicate: KpTabComponent }], ngImport: i0, template: `
|
|
204
204
|
<div class="kp-tabs__row" role="tablist"><ng-content/></div>
|
|
205
205
|
<div class="kp-tabs__more"><ng-content select="[kpTabsMore]"/></div>
|
|
206
|
-
`, isInline: true, styles: [":host{position:relative;display:flex;align-items:stretch;border-bottom:1px solid var(--kp-color-tabs-track-border
|
|
206
|
+
`, isInline: true, styles: [":host{position:relative;display:flex;align-items:stretch;border-bottom:1px solid var(--kp-color-tabs-track-border);font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif)}.kp-tabs__row{display:flex;flex:1 1 auto}:host(.kp-tabs--full-width) .kp-tabs__row>*{flex:1 1 0}.kp-tabs__more{display:inline-flex;flex:0 0 auto}.kp-tabs__more:empty{display:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
207
207
|
}
|
|
208
208
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpTabsComponent, decorators: [{
|
|
209
209
|
type: Component,
|
|
@@ -213,7 +213,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
213
213
|
}, template: `
|
|
214
214
|
<div class="kp-tabs__row" role="tablist"><ng-content/></div>
|
|
215
215
|
<div class="kp-tabs__more"><ng-content select="[kpTabsMore]"/></div>
|
|
216
|
-
`, styles: [":host{position:relative;display:flex;align-items:stretch;border-bottom:1px solid var(--kp-color-tabs-track-border
|
|
216
|
+
`, styles: [":host{position:relative;display:flex;align-items:stretch;border-bottom:1px solid var(--kp-color-tabs-track-border);font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif)}.kp-tabs__row{display:flex;flex:1 1 auto}:host(.kp-tabs--full-width) .kp-tabs__row>*{flex:1 1 0}.kp-tabs__more{display:inline-flex;flex:0 0 auto}.kp-tabs__more:empty{display:none}\n"] }]
|
|
217
217
|
}], propDecorators: { size: [{
|
|
218
218
|
type: Input
|
|
219
219
|
}], fullWidth: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kanso-protocol-tabs.mjs","sources":["../../../../../packages/components/tabs/src/tab.component.ts","../../../../../packages/components/tabs/src/tabs.component.ts","../../../../../packages/components/tabs/src/kanso-protocol-tabs.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n Output,\n inject,\n} from '@angular/core';\n\nexport type KpTabSize = 'sm' | 'md' | 'lg';\n\n/**\n * Kanso Protocol — Tab (atom)\n *\n * Single tab for use inside `<kp-tabs>`. Projects an optional leading icon\n * and an optional trailing badge. The `selected` / `disabled` inputs drive\n * the visual state; hover is handled in CSS and focus via `:focus-visible`\n * on the underlying button.\n *\n * @example\n * <kp-tab label=\"Inbox\" [selected]=\"current === 'inbox'\" (click)=\"current='inbox'\">\n * <svg kpTabIcon .../>\n * <kp-badge kpTabBadge>12</kp-badge>\n * </kp-tab>\n */\n@Component({\n selector: 'kp-tab',\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses',\n '[attr.role]': '\"tab\"',\n '[attr.aria-selected]': 'selected',\n '[attr.aria-disabled]': 'disabled || null',\n '[attr.tabindex]': 'disabled ? -1 : (selected ? 0 : -1)',\n '(click)': 'handleClick($event)',\n '(keydown)': 'handleKey($event)',\n },\n // Inner content is a non-interactive span. The previous <button> inside the\n // role=\"tab\" host triggered axe's nested-interactive rule. Click + keyboard\n // activation now live on the host directly; arrow-key navigation between\n // tabs is owned by the parent <kp-tabs> via roving tabindex (see tabs.component.ts).\n template: `\n <span class=\"kp-tab__btn\">\n <span class=\"kp-tab__icon\" aria-hidden=\"true\">\n <ng-content select=\"[kpTabIcon]\"/>\n </span>\n <span class=\"kp-tab__label\">{{ label }}</span>\n <span class=\"kp-tab__badge\">\n <ng-content select=\"[kpTabBadge]\"/>\n </span>\n </span>\n `,\n styles: [`\n :host {\n display: inline-flex;\n box-sizing: border-box;\n height: var(--kp-tab-h);\n border-bottom: 2px solid var(--kp-color-tabs-tab-underline-rest, transparent);\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n transition: border-color var(--kp-motion-duration-fast) ease, color 120ms ease;\n }\n :host(.kp-tab--full-width) { flex: 1 1 0; }\n\n .kp-tab__btn {\n all: unset;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--kp-tab-gap);\n padding: 0 var(--kp-tab-pad-x);\n height: 100%;\n width: 100%;\n color: var(--kp-color-tabs-tab-fg-rest, var(--kp-color-gray-600));\n cursor: pointer;\n font-size: var(--kp-tab-font-size);\n line-height: var(--kp-tab-line-height);\n font-weight: 500;\n }\n .kp-tab__btn:focus-visible {\n outline: 2px solid var(--kp-color-focus-ring, var(--kp-color-blue-400));\n outline-offset: -2px;\n }\n\n .kp-tab__icon {\n display: inline-flex;\n align-items: center;\n color: var(--kp-color-tabs-tab-icon-rest, var(--kp-color-gray-500));\n }\n .kp-tab__icon:empty { display: none; }\n .kp-tab__icon ::ng-deep svg {\n width: var(--kp-tab-icon-size);\n height: var(--kp-tab-icon-size);\n }\n\n .kp-tab__badge { display: inline-flex; }\n .kp-tab__badge:empty { display: none; }\n\n /* Hover (rest only — disabled / selected handled explicitly) */\n :host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) {\n border-bottom-color: var(--kp-color-tabs-tab-underline-hover, var(--kp-color-gray-300));\n }\n :host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) .kp-tab__btn {\n color: var(--kp-color-tabs-tab-fg-hover, var(--kp-color-gray-900));\n }\n :host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) .kp-tab__icon {\n color: var(--kp-color-tabs-tab-icon-hover, var(--kp-color-gray-700));\n }\n\n /* Selected */\n :host(.kp-tab--selected) {\n border-bottom-color: var(--kp-color-tabs-tab-underline-selected, var(--kp-color-blue-600));\n }\n :host(.kp-tab--selected) .kp-tab__btn {\n color: var(--kp-color-tabs-tab-fg-selected, var(--kp-color-blue-600));\n }\n :host(.kp-tab--selected) .kp-tab__icon {\n color: var(--kp-color-tabs-tab-icon-selected, var(--kp-color-blue-600));\n }\n\n /* Disabled */\n :host(.kp-tab--disabled) {\n border-bottom-color: var(--kp-color-tabs-tab-underline-disabled, transparent);\n }\n :host(.kp-tab--disabled) .kp-tab__btn {\n color: var(--kp-color-tabs-tab-fg-disabled, var(--kp-color-gray-300));\n cursor: not-allowed;\n }\n :host(.kp-tab--disabled) .kp-tab__icon {\n color: var(--kp-color-tabs-tab-icon-disabled, var(--kp-color-gray-300));\n }\n\n /* Sizes */\n :host(.kp-tab--sm) {\n --kp-tab-h: 32px;\n --kp-tab-pad-x: 12px;\n --kp-tab-gap: 6px;\n --kp-tab-font-size: 14px;\n --kp-tab-line-height: 20px;\n --kp-tab-icon-size: 14px;\n }\n :host(.kp-tab--md) {\n --kp-tab-h: 40px;\n --kp-tab-pad-x: 16px;\n --kp-tab-gap: 8px;\n --kp-tab-font-size: 14px;\n --kp-tab-line-height: 20px;\n --kp-tab-icon-size: 16px;\n }\n :host(.kp-tab--lg) {\n --kp-tab-h: 48px;\n --kp-tab-pad-x: 20px;\n --kp-tab-gap: 8px;\n --kp-tab-font-size: 16px;\n --kp-tab-line-height: 24px;\n --kp-tab-icon-size: 16px;\n }\n `],\n})\nexport class KpTabComponent {\n @Input() size: KpTabSize = 'md';\n @Input() label = '';\n @Input() selected = false;\n @Input() disabled = false;\n /** Set by the parent `<kp-tabs>` when `fullWidth` is enabled on the container */\n @Input() fullWidth = false;\n\n @Output() selectedChange = new EventEmitter<boolean>();\n\n /** @internal — exposed so the parent `<kp-tabs>` can focus this tab's\n * inner button as part of its arrow-key roving-tabindex navigation. */\n readonly elementRef = inject(ElementRef) as ElementRef<HTMLElement>;\n\n get hostClasses(): string {\n const c = ['kp-tab', `kp-tab--${this.size}`];\n if (this.selected) c.push('kp-tab--selected');\n if (this.disabled) c.push('kp-tab--disabled');\n if (this.fullWidth) c.push('kp-tab--full-width');\n return c.join(' ');\n }\n\n handleClick(event: MouseEvent): void {\n if (this.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n this.selectedChange.emit(true);\n }\n\n handleKey(event: KeyboardEvent): void {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n if (this.disabled) return;\n event.preventDefault();\n this.selectedChange.emit(true);\n }\n}\n","import {\n AfterContentInit,\n ChangeDetectionStrategy,\n Component,\n ContentChildren,\n ElementRef,\n Input,\n OnDestroy,\n QueryList,\n inject,\n} from '@angular/core';\nimport { Subject, takeUntil } from 'rxjs';\n\nimport { KpTabComponent, KpTabSize } from './tab.component';\n\n/**\n * Kanso Protocol — Tabs (container)\n *\n * Horizontal strip of `<kp-tab>` children with a shared bottom border.\n * Cascades `size` and `fullWidth` down to every projected `<kp-tab>`\n * automatically, so callers only set those two inputs in one place.\n *\n * A `[kpTabsMore]` slot projects an optional \"More ▾\" affordance to the\n * right of the strip — useful when tabs overflow and you want a dropdown\n * to hold the rest.\n *\n * @example\n * <kp-tabs size=\"md\" [fullWidth]=\"false\">\n * <kp-tab label=\"Overview\" [selected]=\"tab === 'overview'\" (selectedChange)=\"tab = 'overview'\"/>\n * <kp-tab label=\"Activity\" [selected]=\"tab === 'activity'\" (selectedChange)=\"tab = 'activity'\"/>\n * <kp-tab label=\"Settings\" [selected]=\"tab === 'settings'\" (selectedChange)=\"tab = 'settings'\"/>\n *\n * <button kpTabsMore>More ▾</button>\n * </kp-tabs>\n */\n@Component({\n selector: 'kp-tabs',\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses',\n '(keydown)': 'onKeyDown($event)',\n },\n // role=\"tablist\" lives on the row that holds the projected <kp-tab> elements.\n // The host also carries an optional [kpTabsMore] sibling slot which is not a\n // tab, so role=tablist on the host would fail axe's aria-required-children.\n template: `\n <div class=\"kp-tabs__row\" role=\"tablist\"><ng-content/></div>\n <div class=\"kp-tabs__more\"><ng-content select=\"[kpTabsMore]\"/></div>\n `,\n styles: [`\n :host {\n position: relative;\n display: flex;\n align-items: stretch;\n border-bottom: 1px solid var(--kp-color-tabs-track-border, var(--kp-color-gray-200));\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n }\n\n .kp-tabs__row {\n display: flex;\n flex: 1 1 auto;\n /* The Tab atoms already carry their own border-bottom; no extra row chrome needed. */\n }\n :host(.kp-tabs--full-width) .kp-tabs__row > * { flex: 1 1 0; }\n\n .kp-tabs__more { display: inline-flex; flex: 0 0 auto; }\n .kp-tabs__more:empty { display: none; }\n `],\n})\nexport class KpTabsComponent implements AfterContentInit, OnDestroy {\n @Input() size: KpTabSize = 'md';\n @Input() fullWidth = false;\n\n @ContentChildren(KpTabComponent) tabs!: QueryList<KpTabComponent>;\n\n private readonly host = inject(ElementRef) as ElementRef<HTMLElement>;\n private readonly destroyed$ = new Subject<void>();\n\n ngAfterContentInit(): void {\n this.applyToTabs();\n this.tabs.changes.pipe(takeUntil(this.destroyed$)).subscribe(() => this.applyToTabs());\n }\n\n ngOnDestroy(): void {\n this.destroyed$.next();\n this.destroyed$.complete();\n }\n\n private applyToTabs(): void {\n this.tabs?.forEach((t) => {\n t.size = this.size;\n t.fullWidth = this.fullWidth;\n });\n }\n\n /**\n * WAI-ARIA tablist keyboard pattern:\n * ArrowLeft / ArrowRight — move to previous / next enabled tab (wraps around)\n * Home / End — jump to first / last enabled tab\n * Enter / Space — select the focused tab (native button handles this too)\n * Disabled tabs are skipped. Selection activates on focus change (automatic\n * activation) since our tab bodies are lightweight to render.\n */\n onKeyDown(event: KeyboardEvent): void {\n const all = this.tabs?.toArray() ?? [];\n const enabled = all.filter((t) => !t.disabled);\n if (!enabled.length) return;\n\n const active = this.activeTabIndex(enabled);\n let next: number | null = null;\n switch (event.key) {\n case 'ArrowRight': next = (active + 1) % enabled.length; break;\n case 'ArrowLeft': next = (active - 1 + enabled.length) % enabled.length; break;\n case 'Home': next = 0; break;\n case 'End': next = enabled.length - 1; break;\n default: return;\n }\n event.preventDefault();\n const target = enabled[next];\n // \"Consumer owns state\" — same pattern as click. Emit (selectedChange)\n // on the target tab and let the parent's [selected] binding propagate.\n if (!target.selected) target.selectedChange.emit(true);\n this.focusTab(target);\n }\n\n private activeTabIndex(enabled: KpTabComponent[]): number {\n const doc = this.host.nativeElement.ownerDocument;\n const focused = doc?.activeElement ?? null;\n const byFocus = enabled.findIndex((t) => t.elementRef.nativeElement.contains(focused));\n if (byFocus >= 0) return byFocus;\n const bySelection = enabled.findIndex((t) => t.selected);\n return bySelection >= 0 ? bySelection : 0;\n }\n\n private focusTab(tab: KpTabComponent): void {\n // The host element itself is the focusable target — it carries role=\"tab\"\n // and tabindex. The inner span is non-interactive (axe nested-interactive).\n tab.elementRef.nativeElement.focus();\n }\n\n get hostClasses(): string {\n const c = ['kp-tabs'];\n if (this.fullWidth) c.push('kp-tabs--full-width');\n return c.join(' ');\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"kanso-protocol-tabs.mjs","sources":["../../../../../packages/components/tabs/src/tab.component.ts","../../../../../packages/components/tabs/src/tabs.component.ts","../../../../../packages/components/tabs/src/kanso-protocol-tabs.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n Output,\n inject,\n} from '@angular/core';\n\nexport type KpTabSize = 'sm' | 'md' | 'lg';\n\n/**\n * Kanso Protocol — Tab (atom)\n *\n * Single tab for use inside `<kp-tabs>`. Projects an optional leading icon\n * and an optional trailing badge. The `selected` / `disabled` inputs drive\n * the visual state; hover is handled in CSS and focus via `:focus-visible`\n * on the underlying button.\n *\n * @example\n * <kp-tab label=\"Inbox\" [selected]=\"current === 'inbox'\" (click)=\"current='inbox'\">\n * <svg kpTabIcon .../>\n * <kp-badge kpTabBadge>12</kp-badge>\n * </kp-tab>\n */\n@Component({\n selector: 'kp-tab',\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses',\n '[attr.role]': '\"tab\"',\n '[attr.aria-selected]': 'selected',\n '[attr.aria-disabled]': 'disabled || null',\n '[attr.tabindex]': 'disabled ? -1 : (selected ? 0 : -1)',\n '(click)': 'handleClick($event)',\n '(keydown)': 'handleKey($event)',\n },\n // Inner content is a non-interactive span. The previous <button> inside the\n // role=\"tab\" host triggered axe's nested-interactive rule. Click + keyboard\n // activation now live on the host directly; arrow-key navigation between\n // tabs is owned by the parent <kp-tabs> via roving tabindex (see tabs.component.ts).\n template: `\n <span class=\"kp-tab__btn\">\n <span class=\"kp-tab__icon\" aria-hidden=\"true\">\n <ng-content select=\"[kpTabIcon]\"/>\n </span>\n <span class=\"kp-tab__label\">{{ label }}</span>\n <span class=\"kp-tab__badge\">\n <ng-content select=\"[kpTabBadge]\"/>\n </span>\n </span>\n `,\n styles: [`\n :host {\n display: inline-flex;\n box-sizing: border-box;\n height: var(--kp-tab-h);\n border-bottom: 2px solid var(--kp-color-tabs-tab-underline-rest, transparent);\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n transition: border-color var(--kp-motion-duration-fast) ease, color 120ms ease;\n }\n :host(.kp-tab--full-width) { flex: 1 1 0; }\n\n .kp-tab__btn {\n all: unset;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--kp-tab-gap);\n padding: 0 var(--kp-tab-pad-x);\n height: 100%;\n width: 100%;\n color: var(--kp-color-tabs-tab-fg-rest);\n cursor: pointer;\n font-size: var(--kp-tab-font-size);\n line-height: var(--kp-tab-line-height);\n font-weight: 500;\n }\n .kp-tab__btn:focus-visible {\n outline: 2px solid var(--kp-color-focus-ring);\n outline-offset: -2px;\n }\n\n .kp-tab__icon {\n display: inline-flex;\n align-items: center;\n color: var(--kp-color-tabs-tab-icon-rest);\n }\n .kp-tab__icon:empty { display: none; }\n .kp-tab__icon ::ng-deep svg {\n width: var(--kp-tab-icon-size);\n height: var(--kp-tab-icon-size);\n }\n\n .kp-tab__badge { display: inline-flex; }\n .kp-tab__badge:empty { display: none; }\n\n /* Hover (rest only — disabled / selected handled explicitly) */\n :host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) {\n border-bottom-color: var(--kp-color-tabs-tab-underline-hover);\n }\n :host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) .kp-tab__btn {\n color: var(--kp-color-tabs-tab-fg-hover);\n }\n :host(:not(.kp-tab--selected):not(.kp-tab--disabled):hover) .kp-tab__icon {\n color: var(--kp-color-tabs-tab-icon-hover);\n }\n\n /* Selected */\n :host(.kp-tab--selected) {\n border-bottom-color: var(--kp-color-tabs-tab-underline-selected);\n }\n :host(.kp-tab--selected) .kp-tab__btn {\n color: var(--kp-color-tabs-tab-fg-selected);\n }\n :host(.kp-tab--selected) .kp-tab__icon {\n color: var(--kp-color-tabs-tab-icon-selected);\n }\n\n /* Disabled */\n :host(.kp-tab--disabled) {\n border-bottom-color: var(--kp-color-tabs-tab-underline-disabled, transparent);\n }\n :host(.kp-tab--disabled) .kp-tab__btn {\n color: var(--kp-color-tabs-tab-fg-disabled);\n cursor: not-allowed;\n }\n :host(.kp-tab--disabled) .kp-tab__icon {\n color: var(--kp-color-tabs-tab-icon-disabled);\n }\n\n /* Sizes */\n :host(.kp-tab--sm) {\n --kp-tab-h: 32px;\n --kp-tab-pad-x: 12px;\n --kp-tab-gap: 6px;\n --kp-tab-font-size: 14px;\n --kp-tab-line-height: 20px;\n --kp-tab-icon-size: 14px;\n }\n :host(.kp-tab--md) {\n --kp-tab-h: 40px;\n --kp-tab-pad-x: 16px;\n --kp-tab-gap: 8px;\n --kp-tab-font-size: 14px;\n --kp-tab-line-height: 20px;\n --kp-tab-icon-size: 16px;\n }\n :host(.kp-tab--lg) {\n --kp-tab-h: 48px;\n --kp-tab-pad-x: 20px;\n --kp-tab-gap: 8px;\n --kp-tab-font-size: 16px;\n --kp-tab-line-height: 24px;\n --kp-tab-icon-size: 16px;\n }\n `],\n})\nexport class KpTabComponent {\n @Input() size: KpTabSize = 'md';\n @Input() label = '';\n @Input() selected = false;\n @Input() disabled = false;\n /** Set by the parent `<kp-tabs>` when `fullWidth` is enabled on the container */\n @Input() fullWidth = false;\n\n @Output() selectedChange = new EventEmitter<boolean>();\n\n /** @internal — exposed so the parent `<kp-tabs>` can focus this tab's\n * inner button as part of its arrow-key roving-tabindex navigation. */\n readonly elementRef = inject(ElementRef) as ElementRef<HTMLElement>;\n\n get hostClasses(): string {\n const c = ['kp-tab', `kp-tab--${this.size}`];\n if (this.selected) c.push('kp-tab--selected');\n if (this.disabled) c.push('kp-tab--disabled');\n if (this.fullWidth) c.push('kp-tab--full-width');\n return c.join(' ');\n }\n\n handleClick(event: MouseEvent): void {\n if (this.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n this.selectedChange.emit(true);\n }\n\n handleKey(event: KeyboardEvent): void {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n if (this.disabled) return;\n event.preventDefault();\n this.selectedChange.emit(true);\n }\n}\n","import {\n AfterContentInit,\n ChangeDetectionStrategy,\n Component,\n ContentChildren,\n ElementRef,\n Input,\n OnDestroy,\n QueryList,\n inject,\n} from '@angular/core';\nimport { Subject, takeUntil } from 'rxjs';\n\nimport { KpTabComponent, KpTabSize } from './tab.component';\n\n/**\n * Kanso Protocol — Tabs (container)\n *\n * Horizontal strip of `<kp-tab>` children with a shared bottom border.\n * Cascades `size` and `fullWidth` down to every projected `<kp-tab>`\n * automatically, so callers only set those two inputs in one place.\n *\n * A `[kpTabsMore]` slot projects an optional \"More ▾\" affordance to the\n * right of the strip — useful when tabs overflow and you want a dropdown\n * to hold the rest.\n *\n * @example\n * <kp-tabs size=\"md\" [fullWidth]=\"false\">\n * <kp-tab label=\"Overview\" [selected]=\"tab === 'overview'\" (selectedChange)=\"tab = 'overview'\"/>\n * <kp-tab label=\"Activity\" [selected]=\"tab === 'activity'\" (selectedChange)=\"tab = 'activity'\"/>\n * <kp-tab label=\"Settings\" [selected]=\"tab === 'settings'\" (selectedChange)=\"tab = 'settings'\"/>\n *\n * <button kpTabsMore>More ▾</button>\n * </kp-tabs>\n */\n@Component({\n selector: 'kp-tabs',\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses',\n '(keydown)': 'onKeyDown($event)',\n },\n // role=\"tablist\" lives on the row that holds the projected <kp-tab> elements.\n // The host also carries an optional [kpTabsMore] sibling slot which is not a\n // tab, so role=tablist on the host would fail axe's aria-required-children.\n template: `\n <div class=\"kp-tabs__row\" role=\"tablist\"><ng-content/></div>\n <div class=\"kp-tabs__more\"><ng-content select=\"[kpTabsMore]\"/></div>\n `,\n styles: [`\n :host {\n position: relative;\n display: flex;\n align-items: stretch;\n border-bottom: 1px solid var(--kp-color-tabs-track-border);\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n }\n\n .kp-tabs__row {\n display: flex;\n flex: 1 1 auto;\n /* The Tab atoms already carry their own border-bottom; no extra row chrome needed. */\n }\n :host(.kp-tabs--full-width) .kp-tabs__row > * { flex: 1 1 0; }\n\n .kp-tabs__more { display: inline-flex; flex: 0 0 auto; }\n .kp-tabs__more:empty { display: none; }\n `],\n})\nexport class KpTabsComponent implements AfterContentInit, OnDestroy {\n @Input() size: KpTabSize = 'md';\n @Input() fullWidth = false;\n\n @ContentChildren(KpTabComponent) tabs!: QueryList<KpTabComponent>;\n\n private readonly host = inject(ElementRef) as ElementRef<HTMLElement>;\n private readonly destroyed$ = new Subject<void>();\n\n ngAfterContentInit(): void {\n this.applyToTabs();\n this.tabs.changes.pipe(takeUntil(this.destroyed$)).subscribe(() => this.applyToTabs());\n }\n\n ngOnDestroy(): void {\n this.destroyed$.next();\n this.destroyed$.complete();\n }\n\n private applyToTabs(): void {\n this.tabs?.forEach((t) => {\n t.size = this.size;\n t.fullWidth = this.fullWidth;\n });\n }\n\n /**\n * WAI-ARIA tablist keyboard pattern:\n * ArrowLeft / ArrowRight — move to previous / next enabled tab (wraps around)\n * Home / End — jump to first / last enabled tab\n * Enter / Space — select the focused tab (native button handles this too)\n * Disabled tabs are skipped. Selection activates on focus change (automatic\n * activation) since our tab bodies are lightweight to render.\n */\n onKeyDown(event: KeyboardEvent): void {\n const all = this.tabs?.toArray() ?? [];\n const enabled = all.filter((t) => !t.disabled);\n if (!enabled.length) return;\n\n const active = this.activeTabIndex(enabled);\n let next: number | null = null;\n switch (event.key) {\n case 'ArrowRight': next = (active + 1) % enabled.length; break;\n case 'ArrowLeft': next = (active - 1 + enabled.length) % enabled.length; break;\n case 'Home': next = 0; break;\n case 'End': next = enabled.length - 1; break;\n default: return;\n }\n event.preventDefault();\n const target = enabled[next];\n // \"Consumer owns state\" — same pattern as click. Emit (selectedChange)\n // on the target tab and let the parent's [selected] binding propagate.\n if (!target.selected) target.selectedChange.emit(true);\n this.focusTab(target);\n }\n\n private activeTabIndex(enabled: KpTabComponent[]): number {\n const doc = this.host.nativeElement.ownerDocument;\n const focused = doc?.activeElement ?? null;\n const byFocus = enabled.findIndex((t) => t.elementRef.nativeElement.contains(focused));\n if (byFocus >= 0) return byFocus;\n const bySelection = enabled.findIndex((t) => t.selected);\n return bySelection >= 0 ? bySelection : 0;\n }\n\n private focusTab(tab: KpTabComponent): void {\n // The host element itself is the focusable target — it carries role=\"tab\"\n // and tabindex. The inner span is non-interactive (axe nested-interactive).\n tab.elementRef.nativeElement.focus();\n }\n\n get hostClasses(): string {\n const c = ['kp-tabs'];\n if (this.fullWidth) c.push('kp-tabs--full-width');\n return c.join(' ');\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanso-protocol/tabs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@angular/core": "^18.0.0",
|
|
7
7
|
"@angular/common": "^18.0.0",
|
|
8
|
-
"@kanso-protocol/core": "
|
|
8
|
+
"@kanso-protocol/core": ">=0.5.2"
|
|
9
9
|
},
|
|
10
10
|
"description": "Kanso Protocol — tabs (component).",
|
|
11
11
|
"author": "GregNBlack",
|