@kanso-protocol/theme-toggle 2.0.1 → 2.0.3
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.
|
@@ -2,6 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { EventEmitter, signal, inject, ElementRef, HostListener, ViewChild, Output, Input, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { DOCUMENT } from '@angular/common';
|
|
4
4
|
import { KpIconComponent } from '@kanso-protocol/icon';
|
|
5
|
+
import { findPortalTarget } from '@kanso-protocol/core';
|
|
5
6
|
|
|
6
7
|
const THEMES = ['light', 'dark', 'system'];
|
|
7
8
|
let nextRadioGroupId = 0;
|
|
@@ -44,13 +45,16 @@ class KpThemeToggleComponent {
|
|
|
44
45
|
return;
|
|
45
46
|
const menu = this.menuEl?.nativeElement;
|
|
46
47
|
// Portal menu to <body> so transformed/clipped ancestors can't clip it.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
// Portal to body normally; to nearest open <dialog> when inside one.
|
|
49
|
+
if (menu && this.doc) {
|
|
50
|
+
const target = findPortalTarget(this.hostRef.nativeElement, this.doc);
|
|
51
|
+
if (menu.parentElement !== target) {
|
|
52
|
+
menu.classList.add(`kp-theme-toggle__menu--${this.size}`);
|
|
53
|
+
target.appendChild(menu);
|
|
54
|
+
this.portaledMenu = menu;
|
|
55
|
+
window.addEventListener('scroll', this.reposition, true);
|
|
56
|
+
window.addEventListener('resize', this.reposition);
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
this.positionMenu();
|
|
56
60
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kanso-protocol-theme-toggle.mjs","sources":["../../../../../packages/patterns/theme-toggle/src/theme-toggle.component.ts","../../../../../packages/patterns/theme-toggle/src/kanso-protocol-theme-toggle.ts"],"sourcesContent":["import {\n AfterViewChecked,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n EventEmitter,\n HostListener,\n Input,\n OnDestroy,\n Output,\n ViewChild,\n inject,\n signal,\n} from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\nimport { KpIconComponent } from '@kanso-protocol/icon';\n\nexport type KpThemeToggleVariant = 'icon' | 'segmented' | 'dropdown';\nexport type KpThemeToggleSize = 'sm' | 'md' | 'lg';\nexport type KpThemeValue = 'light' | 'dark' | 'system';\n\nconst THEMES: KpThemeValue[] = ['light', 'dark', 'system'];\n\nlet nextRadioGroupId = 0;\n\n/**\n * Kanso Protocol — ThemeToggle\n *\n * Three presentations for switching between light / dark / system\n * themes:\n * - `icon` — single icon button that cycles\n * - `segmented` — three inline icon segments, one selected\n * - `dropdown` — ghost button showing the current theme label; opens a menu\n *\n * The component emits `themeChange` when the user selects a value;\n * keeping the chosen theme (including `system`) up to the consumer.\n *\n * @example\n * <kp-theme-toggle\n * variant=\"segmented\"\n * [showLabel]=\"true\"\n * [currentTheme]=\"theme()\"\n * (themeChange)=\"theme.set($event)\"\n * />\n */\n@Component({\n selector: 'kp-theme-toggle',\n imports: [KpIconComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { '[class]': 'hostClasses' },\n template: `\n @if (variant === 'icon') {\n <button\n type=\"button\"\n class=\"kp-theme-toggle__icon-btn\"\n [attr.aria-label]=\"'Toggle theme (current: ' + currentTheme + ')'\"\n (click)=\"cycle()\"\n >\n <kp-icon [name]=\"iconName(currentTheme)\" />\n </button>\n } @else if (variant === 'segmented') {\n @if (showLabel) {\n <span class=\"kp-theme-toggle__label\">Theme</span>\n }\n <div class=\"kp-theme-toggle__segments\" role=\"radiogroup\" aria-label=\"Theme\">\n <span class=\"kp-theme-toggle__pill\" aria-hidden=\"true\"></span>\n @for (t of themes; track t) {\n <label\n class=\"kp-theme-toggle__segment\"\n [class.kp-theme-toggle__segment--selected]=\"currentTheme === t\">\n <input\n type=\"radio\"\n class=\"kp-theme-toggle__radio\"\n [name]=\"radioGroupName\"\n [value]=\"t\"\n [checked]=\"currentTheme === t\"\n [attr.aria-label]=\"t\"\n (change)=\"select(t)\"\n />\n <kp-icon [name]=\"iconName(t)\" />\n </label>\n }\n </div>\n } @else {\n @if (showLabel) {\n <span class=\"kp-theme-toggle__label\">Theme</span>\n }\n <div class=\"kp-theme-toggle__dropdown-wrap\">\n <button\n type=\"button\"\n class=\"kp-theme-toggle__dropdown\"\n [class.kp-theme-toggle__dropdown--open]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n (click)=\"toggleOpen()\"\n >\n <kp-icon [name]=\"iconName(currentTheme)\" />\n <span class=\"kp-theme-toggle__dropdown-label\">{{ themeLabel(currentTheme) }}</span>\n <kp-icon name=\"chevron-down\" class=\"kp-theme-toggle__chevron\" />\n </button>\n @if (isOpen()) {\n <div #menu class=\"kp-theme-toggle__menu\" role=\"listbox\" aria-label=\"Theme\">\n @for (t of themes; track t) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"kp-theme-toggle__option\"\n [class.kp-theme-toggle__option--selected]=\"currentTheme === t\"\n [attr.aria-selected]=\"currentTheme === t\"\n (click)=\"selectFromMenu(t)\"\n >\n <kp-icon [name]=\"iconName(t)\" />\n <span>{{ themeLabel(t) }}</span>\n @if (currentTheme === t) {\n <kp-icon name=\"check\" class=\"kp-theme-toggle__option-check\" />\n }\n </button>\n }\n </div>\n }\n </div>\n }\n `,\n styles: [`\n :host {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n }\n\n .kp-theme-toggle__label {\n font-size: 14px;\n color: var(--kp-color-text-default);\n }\n\n :host .ti {\n font-size: var(--kp-theme-glyph, 18px);\n line-height: 1;\n }\n :host .ti.kp-theme-toggle__chevron {\n font-size: 14px;\n color: var(--kp-color-text-muted);\n }\n\n /* Icon variant */\n .kp-theme-toggle__icon-btn {\n all: unset;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--kp-theme-btn, 36px);\n height: var(--kp-theme-btn, 36px);\n border-radius: 8px;\n color: var(--kp-color-text-muted);\n cursor: pointer;\n transition: background var(--kp-motion-duration-fast) ease, color 120ms ease;\n }\n .kp-theme-toggle__icon-btn:hover { background: var(--kp-color-surface-muted); color: var(--kp-color-text-strong); }\n\n /* Segmented variant — adopts the same look as <kp-segmented-control>:\n borderless track, sliding-pill behind 3 icon segments, colors\n sourced from the segmented design tokens (so light/dark theme\n and any future re-tuning of segmented stays in sync between\n both surfaces). */\n .kp-theme-toggle__segments {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border: none;\n border-radius: var(--kp-segmented-radius, 10px);\n background: var(--kp-color-segmented-track-bg);\n }\n .kp-theme-toggle__pill {\n position: absolute;\n top: 2px;\n left: 2px;\n width: var(--kp-theme-seg, 28px);\n height: var(--kp-theme-seg, 28px);\n border-radius: var(--kp-segmented-segment-radius, 8px);\n background: var(--kp-color-segmented-segment-bg-selected);\n box-shadow: var(--kp-elevation-raised);\n transform: translateX(var(--kp-theme-pill-x, 0));\n transition: transform 240ms cubic-bezier(0.32, 0.72, 0, 1);\n pointer-events: none;\n z-index: 0;\n }\n /* Pill stride = segment width + gap (2px). */\n :host(.kp-theme-toggle--theme-light) { --kp-theme-pill-x: 0; }\n :host(.kp-theme-toggle--theme-dark) { --kp-theme-pill-x: calc(var(--kp-theme-seg, 28px) + 2px); }\n :host(.kp-theme-toggle--theme-system) { --kp-theme-pill-x: calc((var(--kp-theme-seg, 28px) + 2px) * 2); }\n\n .kp-theme-toggle__segment {\n position: relative;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--kp-theme-seg, 28px);\n height: var(--kp-theme-seg, 28px);\n border-radius: var(--kp-segmented-segment-radius, 8px);\n color: var(--kp-color-segmented-segment-fg-unselected-rest);\n cursor: pointer;\n transition: color var(--kp-motion-duration-normal) cubic-bezier(0.32, 0.72, 0, 1);\n }\n /* sr-only clip — input stays in a11y tree so axe sees the\n radiogroup's required children; label-wrapping forwards click. */\n .kp-theme-toggle__radio {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n .kp-theme-toggle__segment:hover:not(.kp-theme-toggle__segment--selected) {\n color: var(--kp-color-segmented-segment-fg-unselected-hover);\n }\n .kp-theme-toggle__segment:has(.kp-theme-toggle__radio:focus-visible) {\n outline: 2px solid var(--kp-color-focus-ring);\n outline-offset: 1px;\n }\n .kp-theme-toggle__segment--selected {\n color: var(--kp-color-segmented-segment-fg-selected);\n }\n\n /* Sizes — drive segment dimensions + radius via the same token\n names that <kp-segmented-control> uses, so the matching is\n guaranteed even if those values move later. */\n :host(.kp-theme-toggle--sm) {\n --kp-theme-seg: 24px;\n --kp-segmented-radius: 10px;\n --kp-segmented-segment-radius: 8px;\n }\n :host(.kp-theme-toggle--md) {\n --kp-theme-seg: 28px;\n --kp-segmented-radius: 12px;\n --kp-segmented-segment-radius: 10px;\n }\n :host(.kp-theme-toggle--lg) {\n --kp-theme-seg: 32px;\n --kp-segmented-radius: 14px;\n --kp-segmented-segment-radius: 12px;\n }\n\n /* Dropdown variant */\n .kp-theme-toggle__dropdown {\n all: unset;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 10px;\n border-radius: 8px;\n color: var(--kp-color-text-default);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background var(--kp-motion-duration-fast) ease, color 120ms ease;\n }\n .kp-theme-toggle__dropdown:hover,\n .kp-theme-toggle__dropdown--open {\n background: var(--kp-color-surface-muted);\n color: var(--kp-color-text-strong);\n }\n .kp-theme-toggle__dropdown-label { min-width: 44px; text-align: start; }\n\n .kp-theme-toggle__dropdown-wrap {\n position: relative;\n display: inline-block;\n }\n .kp-theme-toggle__menu {\n position: fixed;\n display: flex;\n flex-direction: column;\n padding: 4px;\n border-radius: 10px;\n background: var(--kp-color-surface-base);\n border: 1px solid var(--kp-color-border-default);\n box-shadow: var(--kp-elevation-overlay);\n z-index: 1000;\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n }\n .kp-theme-toggle__option {\n all: unset;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 10px;\n border-radius: 6px;\n font-size: 13px;\n color: var(--kp-color-text-default);\n cursor: pointer;\n transition: background var(--kp-motion-duration-fast) ease, color 120ms ease;\n }\n .kp-theme-toggle__option:hover {\n background: var(--kp-color-surface-muted);\n color: var(--kp-color-text-strong);\n }\n .kp-theme-toggle__option--selected {\n color: var(--kp-color-text-strong);\n font-weight: 500;\n }\n .kp-theme-toggle__option-check {\n margin-inline-start: auto;\n color: var(--kp-color-accent-primary-fg);\n }\n\n /* Menu icons — match trigger size. Scoped by size class instead of :host\n because the menu is portaled to <body> and escapes the host context. */\n .kp-theme-toggle__menu .ti { font-size: 18px; line-height: 1; }\n .kp-theme-toggle__menu--sm .ti { font-size: 16px; }\n .kp-theme-toggle__menu--md .ti { font-size: 18px; }\n .kp-theme-toggle__menu--lg .ti { font-size: 20px; }\n .kp-theme-toggle__menu .ti.kp-theme-toggle__option-check { font-size: 14px; }\n\n /* Sizes */\n :host(.kp-theme-toggle--sm) {\n --kp-theme-btn: 28px;\n --kp-theme-seg: 24px;\n --kp-theme-glyph: 16px;\n }\n :host(.kp-theme-toggle--md) {\n --kp-theme-btn: 36px;\n --kp-theme-seg: 28px;\n --kp-theme-glyph: 18px;\n }\n :host(.kp-theme-toggle--lg) {\n --kp-theme-btn: 44px;\n --kp-theme-seg: 36px;\n --kp-theme-glyph: 20px;\n }\n `],\n})\nexport class KpThemeToggleComponent implements AfterViewChecked, OnDestroy {\n @Input() variant: KpThemeToggleVariant = 'icon';\n @Input() size: KpThemeToggleSize = 'md';\n @Input() currentTheme: KpThemeValue = 'light';\n @Input() showLabel = false;\n\n @Output() themeChange = new EventEmitter<KpThemeValue>();\n @Output() dropdownClick = new EventEmitter<void>();\n\n readonly themes = THEMES;\n readonly isOpen = signal(false);\n readonly radioGroupName = `kp-theme-toggle-${++nextRadioGroupId}`;\n\n @ViewChild('menu') menuEl?: ElementRef<HTMLElement>;\n\n private readonly hostRef = inject(ElementRef<HTMLElement>);\n private readonly doc = inject(DOCUMENT);\n private portaledMenu: HTMLElement | null = null;\n\n ngAfterViewChecked(): void {\n if (!this.isOpen()) return;\n const menu = this.menuEl?.nativeElement;\n // Portal menu to <body> so transformed/clipped ancestors can't clip it.\n if (menu && this.doc?.body && menu.parentElement !== this.doc.body) {\n // Tag the menu with the current size so size-scoped CSS applies after portal\n menu.classList.add(`kp-theme-toggle__menu--${this.size}`);\n this.doc.body.appendChild(menu);\n this.portaledMenu = menu;\n window.addEventListener('scroll', this.reposition, true);\n window.addEventListener('resize', this.reposition);\n }\n this.positionMenu();\n }\n\n ngOnDestroy(): void {\n this.cleanupMenu();\n }\n\n private cleanupMenu(): void {\n window.removeEventListener('scroll', this.reposition, true);\n window.removeEventListener('resize', this.reposition);\n if (this.portaledMenu && this.portaledMenu.parentElement === this.doc?.body) {\n this.portaledMenu.remove();\n }\n this.portaledMenu = null;\n }\n\n private closeMenu(): void {\n if (!this.isOpen()) return;\n this.cleanupMenu();\n this.isOpen.set(false);\n }\n\n private readonly reposition = () => this.positionMenu();\n\n private positionMenu(): void {\n const menu = this.menuEl?.nativeElement;\n const trigger = this.hostRef.nativeElement.querySelector('.kp-theme-toggle__dropdown') as HTMLElement | null;\n if (!menu || !trigger) return;\n const rect = trigger.getBoundingClientRect();\n menu.style.top = `${rect.bottom + 6}px`;\n menu.style.left = `${rect.left}px`;\n menu.style.right = 'auto';\n menu.style.width = `${rect.width}px`;\n }\n\n @HostListener('document:click', ['$event'])\n onDocClick(event: Event): void {\n if (!this.isOpen()) return;\n const target = event.target as Node;\n const host = this.hostRef.nativeElement;\n const portaled = this.portaledMenu;\n const insideHost = host && host.contains(target);\n const insideMenu = portaled && portaled.contains(target);\n if (!insideHost && !insideMenu) this.closeMenu();\n }\n\n @HostListener('document:keydown.escape')\n onEsc(): void {\n this.closeMenu();\n }\n\n toggleOpen(): void {\n if (this.isOpen()) {\n this.closeMenu();\n } else {\n this.isOpen.set(true);\n this.dropdownClick.emit();\n }\n }\n\n selectFromMenu(t: KpThemeValue): void {\n this.select(t);\n this.closeMenu();\n }\n\n get hostClasses(): string {\n return `kp-theme-toggle kp-theme-toggle--${this.variant} kp-theme-toggle--${this.size} kp-theme-toggle--theme-${this.currentTheme}`;\n }\n\n cycle(): void {\n const idx = THEMES.indexOf(this.currentTheme);\n const next = THEMES[(idx + 1) % THEMES.length];\n this.currentTheme = next;\n this.themeChange.emit(next);\n }\n\n select(t: KpThemeValue): void {\n if (t === this.currentTheme) return;\n this.currentTheme = t;\n this.themeChange.emit(t);\n }\n\n themeLabel(t: KpThemeValue): string {\n return t === 'light' ? 'Light' : t === 'dark' ? 'Dark' : 'System';\n }\n\n iconName(t: KpThemeValue): string {\n return t === 'light' ? 'sun-high' : t === 'dark' ? 'moon' : 'device-desktop';\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-theme-toggle.mjs","sources":["../../../../../packages/patterns/theme-toggle/src/theme-toggle.component.ts","../../../../../packages/patterns/theme-toggle/src/kanso-protocol-theme-toggle.ts"],"sourcesContent":["import {\n AfterViewChecked,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n EventEmitter,\n HostListener,\n Input,\n OnDestroy,\n Output,\n ViewChild,\n inject,\n signal,\n} from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\nimport { KpIconComponent } from '@kanso-protocol/icon';\nimport { findPortalTarget } from '@kanso-protocol/core';\n\nexport type KpThemeToggleVariant = 'icon' | 'segmented' | 'dropdown';\nexport type KpThemeToggleSize = 'sm' | 'md' | 'lg';\nexport type KpThemeValue = 'light' | 'dark' | 'system';\n\nconst THEMES: KpThemeValue[] = ['light', 'dark', 'system'];\n\nlet nextRadioGroupId = 0;\n\n/**\n * Kanso Protocol — ThemeToggle\n *\n * Three presentations for switching between light / dark / system\n * themes:\n * - `icon` — single icon button that cycles\n * - `segmented` — three inline icon segments, one selected\n * - `dropdown` — ghost button showing the current theme label; opens a menu\n *\n * The component emits `themeChange` when the user selects a value;\n * keeping the chosen theme (including `system`) up to the consumer.\n *\n * @example\n * <kp-theme-toggle\n * variant=\"segmented\"\n * [showLabel]=\"true\"\n * [currentTheme]=\"theme()\"\n * (themeChange)=\"theme.set($event)\"\n * />\n */\n@Component({\n selector: 'kp-theme-toggle',\n imports: [KpIconComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { '[class]': 'hostClasses' },\n template: `\n @if (variant === 'icon') {\n <button\n type=\"button\"\n class=\"kp-theme-toggle__icon-btn\"\n [attr.aria-label]=\"'Toggle theme (current: ' + currentTheme + ')'\"\n (click)=\"cycle()\"\n >\n <kp-icon [name]=\"iconName(currentTheme)\" />\n </button>\n } @else if (variant === 'segmented') {\n @if (showLabel) {\n <span class=\"kp-theme-toggle__label\">Theme</span>\n }\n <div class=\"kp-theme-toggle__segments\" role=\"radiogroup\" aria-label=\"Theme\">\n <span class=\"kp-theme-toggle__pill\" aria-hidden=\"true\"></span>\n @for (t of themes; track t) {\n <label\n class=\"kp-theme-toggle__segment\"\n [class.kp-theme-toggle__segment--selected]=\"currentTheme === t\">\n <input\n type=\"radio\"\n class=\"kp-theme-toggle__radio\"\n [name]=\"radioGroupName\"\n [value]=\"t\"\n [checked]=\"currentTheme === t\"\n [attr.aria-label]=\"t\"\n (change)=\"select(t)\"\n />\n <kp-icon [name]=\"iconName(t)\" />\n </label>\n }\n </div>\n } @else {\n @if (showLabel) {\n <span class=\"kp-theme-toggle__label\">Theme</span>\n }\n <div class=\"kp-theme-toggle__dropdown-wrap\">\n <button\n type=\"button\"\n class=\"kp-theme-toggle__dropdown\"\n [class.kp-theme-toggle__dropdown--open]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n (click)=\"toggleOpen()\"\n >\n <kp-icon [name]=\"iconName(currentTheme)\" />\n <span class=\"kp-theme-toggle__dropdown-label\">{{ themeLabel(currentTheme) }}</span>\n <kp-icon name=\"chevron-down\" class=\"kp-theme-toggle__chevron\" />\n </button>\n @if (isOpen()) {\n <div #menu class=\"kp-theme-toggle__menu\" role=\"listbox\" aria-label=\"Theme\">\n @for (t of themes; track t) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"kp-theme-toggle__option\"\n [class.kp-theme-toggle__option--selected]=\"currentTheme === t\"\n [attr.aria-selected]=\"currentTheme === t\"\n (click)=\"selectFromMenu(t)\"\n >\n <kp-icon [name]=\"iconName(t)\" />\n <span>{{ themeLabel(t) }}</span>\n @if (currentTheme === t) {\n <kp-icon name=\"check\" class=\"kp-theme-toggle__option-check\" />\n }\n </button>\n }\n </div>\n }\n </div>\n }\n `,\n styles: [`\n :host {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n }\n\n .kp-theme-toggle__label {\n font-size: 14px;\n color: var(--kp-color-text-default);\n }\n\n :host .ti {\n font-size: var(--kp-theme-glyph, 18px);\n line-height: 1;\n }\n :host .ti.kp-theme-toggle__chevron {\n font-size: 14px;\n color: var(--kp-color-text-muted);\n }\n\n /* Icon variant */\n .kp-theme-toggle__icon-btn {\n all: unset;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--kp-theme-btn, 36px);\n height: var(--kp-theme-btn, 36px);\n border-radius: 8px;\n color: var(--kp-color-text-muted);\n cursor: pointer;\n transition: background var(--kp-motion-duration-fast) ease, color 120ms ease;\n }\n .kp-theme-toggle__icon-btn:hover { background: var(--kp-color-surface-muted); color: var(--kp-color-text-strong); }\n\n /* Segmented variant — adopts the same look as <kp-segmented-control>:\n borderless track, sliding-pill behind 3 icon segments, colors\n sourced from the segmented design tokens (so light/dark theme\n and any future re-tuning of segmented stays in sync between\n both surfaces). */\n .kp-theme-toggle__segments {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border: none;\n border-radius: var(--kp-segmented-radius, 10px);\n background: var(--kp-color-segmented-track-bg);\n }\n .kp-theme-toggle__pill {\n position: absolute;\n top: 2px;\n left: 2px;\n width: var(--kp-theme-seg, 28px);\n height: var(--kp-theme-seg, 28px);\n border-radius: var(--kp-segmented-segment-radius, 8px);\n background: var(--kp-color-segmented-segment-bg-selected);\n box-shadow: var(--kp-elevation-raised);\n transform: translateX(var(--kp-theme-pill-x, 0));\n transition: transform 240ms cubic-bezier(0.32, 0.72, 0, 1);\n pointer-events: none;\n z-index: 0;\n }\n /* Pill stride = segment width + gap (2px). */\n :host(.kp-theme-toggle--theme-light) { --kp-theme-pill-x: 0; }\n :host(.kp-theme-toggle--theme-dark) { --kp-theme-pill-x: calc(var(--kp-theme-seg, 28px) + 2px); }\n :host(.kp-theme-toggle--theme-system) { --kp-theme-pill-x: calc((var(--kp-theme-seg, 28px) + 2px) * 2); }\n\n .kp-theme-toggle__segment {\n position: relative;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--kp-theme-seg, 28px);\n height: var(--kp-theme-seg, 28px);\n border-radius: var(--kp-segmented-segment-radius, 8px);\n color: var(--kp-color-segmented-segment-fg-unselected-rest);\n cursor: pointer;\n transition: color var(--kp-motion-duration-normal) cubic-bezier(0.32, 0.72, 0, 1);\n }\n /* sr-only clip — input stays in a11y tree so axe sees the\n radiogroup's required children; label-wrapping forwards click. */\n .kp-theme-toggle__radio {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n .kp-theme-toggle__segment:hover:not(.kp-theme-toggle__segment--selected) {\n color: var(--kp-color-segmented-segment-fg-unselected-hover);\n }\n .kp-theme-toggle__segment:has(.kp-theme-toggle__radio:focus-visible) {\n outline: 2px solid var(--kp-color-focus-ring);\n outline-offset: 1px;\n }\n .kp-theme-toggle__segment--selected {\n color: var(--kp-color-segmented-segment-fg-selected);\n }\n\n /* Sizes — drive segment dimensions + radius via the same token\n names that <kp-segmented-control> uses, so the matching is\n guaranteed even if those values move later. */\n :host(.kp-theme-toggle--sm) {\n --kp-theme-seg: 24px;\n --kp-segmented-radius: 10px;\n --kp-segmented-segment-radius: 8px;\n }\n :host(.kp-theme-toggle--md) {\n --kp-theme-seg: 28px;\n --kp-segmented-radius: 12px;\n --kp-segmented-segment-radius: 10px;\n }\n :host(.kp-theme-toggle--lg) {\n --kp-theme-seg: 32px;\n --kp-segmented-radius: 14px;\n --kp-segmented-segment-radius: 12px;\n }\n\n /* Dropdown variant */\n .kp-theme-toggle__dropdown {\n all: unset;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 10px;\n border-radius: 8px;\n color: var(--kp-color-text-default);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background var(--kp-motion-duration-fast) ease, color 120ms ease;\n }\n .kp-theme-toggle__dropdown:hover,\n .kp-theme-toggle__dropdown--open {\n background: var(--kp-color-surface-muted);\n color: var(--kp-color-text-strong);\n }\n .kp-theme-toggle__dropdown-label { min-width: 44px; text-align: start; }\n\n .kp-theme-toggle__dropdown-wrap {\n position: relative;\n display: inline-block;\n }\n .kp-theme-toggle__menu {\n position: fixed;\n display: flex;\n flex-direction: column;\n padding: 4px;\n border-radius: 10px;\n background: var(--kp-color-surface-base);\n border: 1px solid var(--kp-color-border-default);\n box-shadow: var(--kp-elevation-overlay);\n z-index: 1000;\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n }\n .kp-theme-toggle__option {\n all: unset;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 10px;\n border-radius: 6px;\n font-size: 13px;\n color: var(--kp-color-text-default);\n cursor: pointer;\n transition: background var(--kp-motion-duration-fast) ease, color 120ms ease;\n }\n .kp-theme-toggle__option:hover {\n background: var(--kp-color-surface-muted);\n color: var(--kp-color-text-strong);\n }\n .kp-theme-toggle__option--selected {\n color: var(--kp-color-text-strong);\n font-weight: 500;\n }\n .kp-theme-toggle__option-check {\n margin-inline-start: auto;\n color: var(--kp-color-accent-primary-fg);\n }\n\n /* Menu icons — match trigger size. Scoped by size class instead of :host\n because the menu is portaled to <body> and escapes the host context. */\n .kp-theme-toggle__menu .ti { font-size: 18px; line-height: 1; }\n .kp-theme-toggle__menu--sm .ti { font-size: 16px; }\n .kp-theme-toggle__menu--md .ti { font-size: 18px; }\n .kp-theme-toggle__menu--lg .ti { font-size: 20px; }\n .kp-theme-toggle__menu .ti.kp-theme-toggle__option-check { font-size: 14px; }\n\n /* Sizes */\n :host(.kp-theme-toggle--sm) {\n --kp-theme-btn: 28px;\n --kp-theme-seg: 24px;\n --kp-theme-glyph: 16px;\n }\n :host(.kp-theme-toggle--md) {\n --kp-theme-btn: 36px;\n --kp-theme-seg: 28px;\n --kp-theme-glyph: 18px;\n }\n :host(.kp-theme-toggle--lg) {\n --kp-theme-btn: 44px;\n --kp-theme-seg: 36px;\n --kp-theme-glyph: 20px;\n }\n `],\n})\nexport class KpThemeToggleComponent implements AfterViewChecked, OnDestroy {\n @Input() variant: KpThemeToggleVariant = 'icon';\n @Input() size: KpThemeToggleSize = 'md';\n @Input() currentTheme: KpThemeValue = 'light';\n @Input() showLabel = false;\n\n @Output() themeChange = new EventEmitter<KpThemeValue>();\n @Output() dropdownClick = new EventEmitter<void>();\n\n readonly themes = THEMES;\n readonly isOpen = signal(false);\n readonly radioGroupName = `kp-theme-toggle-${++nextRadioGroupId}`;\n\n @ViewChild('menu') menuEl?: ElementRef<HTMLElement>;\n\n private readonly hostRef = inject(ElementRef<HTMLElement>);\n private readonly doc = inject(DOCUMENT);\n private portaledMenu: HTMLElement | null = null;\n\n ngAfterViewChecked(): void {\n if (!this.isOpen()) return;\n const menu = this.menuEl?.nativeElement;\n // Portal menu to <body> so transformed/clipped ancestors can't clip it.\n // Portal to body normally; to nearest open <dialog> when inside one.\n if (menu && this.doc) {\n const target = findPortalTarget(this.hostRef.nativeElement, this.doc);\n if (menu.parentElement !== target) {\n menu.classList.add(`kp-theme-toggle__menu--${this.size}`);\n target.appendChild(menu);\n this.portaledMenu = menu;\n window.addEventListener('scroll', this.reposition, true);\n window.addEventListener('resize', this.reposition);\n }\n }\n this.positionMenu();\n }\n\n ngOnDestroy(): void {\n this.cleanupMenu();\n }\n\n private cleanupMenu(): void {\n window.removeEventListener('scroll', this.reposition, true);\n window.removeEventListener('resize', this.reposition);\n if (this.portaledMenu && this.portaledMenu.parentElement === this.doc?.body) {\n this.portaledMenu.remove();\n }\n this.portaledMenu = null;\n }\n\n private closeMenu(): void {\n if (!this.isOpen()) return;\n this.cleanupMenu();\n this.isOpen.set(false);\n }\n\n private readonly reposition = () => this.positionMenu();\n\n private positionMenu(): void {\n const menu = this.menuEl?.nativeElement;\n const trigger = this.hostRef.nativeElement.querySelector('.kp-theme-toggle__dropdown') as HTMLElement | null;\n if (!menu || !trigger) return;\n const rect = trigger.getBoundingClientRect();\n menu.style.top = `${rect.bottom + 6}px`;\n menu.style.left = `${rect.left}px`;\n menu.style.right = 'auto';\n menu.style.width = `${rect.width}px`;\n }\n\n @HostListener('document:click', ['$event'])\n onDocClick(event: Event): void {\n if (!this.isOpen()) return;\n const target = event.target as Node;\n const host = this.hostRef.nativeElement;\n const portaled = this.portaledMenu;\n const insideHost = host && host.contains(target);\n const insideMenu = portaled && portaled.contains(target);\n if (!insideHost && !insideMenu) this.closeMenu();\n }\n\n @HostListener('document:keydown.escape')\n onEsc(): void {\n this.closeMenu();\n }\n\n toggleOpen(): void {\n if (this.isOpen()) {\n this.closeMenu();\n } else {\n this.isOpen.set(true);\n this.dropdownClick.emit();\n }\n }\n\n selectFromMenu(t: KpThemeValue): void {\n this.select(t);\n this.closeMenu();\n }\n\n get hostClasses(): string {\n return `kp-theme-toggle kp-theme-toggle--${this.variant} kp-theme-toggle--${this.size} kp-theme-toggle--theme-${this.currentTheme}`;\n }\n\n cycle(): void {\n const idx = THEMES.indexOf(this.currentTheme);\n const next = THEMES[(idx + 1) % THEMES.length];\n this.currentTheme = next;\n this.themeChange.emit(next);\n }\n\n select(t: KpThemeValue): void {\n if (t === this.currentTheme) return;\n this.currentTheme = t;\n this.themeChange.emit(t);\n }\n\n themeLabel(t: KpThemeValue): string {\n return t === 'light' ? 'Light' : t === 'dark' ? 'Dark' : 'System';\n }\n\n iconName(t: KpThemeValue): string {\n return t === 'light' ? 'sun-high' : t === 'dark' ? 'moon' : 'device-desktop';\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAsBA,MAAM,MAAM,GAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC;AAE1D,IAAI,gBAAgB,GAAG,CAAC;AAExB;;;;;;;;;;;;;;;;;;;AAmBG;MAsSU,sBAAsB,CAAA;IACxB,OAAO,GAAyB,MAAM;IACtC,IAAI,GAAsB,IAAI;IAC9B,YAAY,GAAiB,OAAO;IACpC,SAAS,GAAG,KAAK;AAEhB,IAAA,WAAW,GAAG,IAAI,YAAY,EAAgB;AAC9C,IAAA,aAAa,GAAG,IAAI,YAAY,EAAQ;IAEzC,MAAM,GAAG,MAAM;AACf,IAAA,MAAM,GAAG,MAAM,CAAC,KAAK,6EAAC;AACtB,IAAA,cAAc,GAAG,CAAA,gBAAA,EAAmB,EAAE,gBAAgB,EAAE;AAE9C,IAAA,MAAM;AAER,IAAA,OAAO,GAAG,MAAM,EAAC,UAAuB,EAAC;AACzC,IAAA,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC/B,YAAY,GAAuB,IAAI;IAE/C,kBAAkB,GAAA;AAChB,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE;AACpB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,aAAa;;;AAGvC,QAAA,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE;AACpB,YAAA,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC;AACrE,YAAA,IAAI,IAAI,CAAC,aAAa,KAAK,MAAM,EAAE;gBACjC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA,uBAAA,EAA0B,IAAI,CAAC,IAAI,CAAA,CAAE,CAAC;AACzD,gBAAA,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC;AACxB,gBAAA,IAAI,CAAC,YAAY,GAAG,IAAI;gBACxB,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC;gBACxD,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC;YACpD;QACF;QACA,IAAI,CAAC,YAAY,EAAE;IACrB;IAEA,WAAW,GAAA;QACT,IAAI,CAAC,WAAW,EAAE;IACpB;IAEQ,WAAW,GAAA;QACjB,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC;QAC3D,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC;AACrD,QAAA,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC,aAAa,KAAK,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE;AAC3E,YAAA,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;QAC5B;AACA,QAAA,IAAI,CAAC,YAAY,GAAG,IAAI;IAC1B;IAEQ,SAAS,GAAA;AACf,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE;QACpB,IAAI,CAAC,WAAW,EAAE;AAClB,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;IACxB;IAEiB,UAAU,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE;IAE/C,YAAY,GAAA;AAClB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,aAAa;AACvC,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,4BAA4B,CAAuB;AAC5G,QAAA,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO;YAAE;AACvB,QAAA,MAAM,IAAI,GAAG,OAAO,CAAC,qBAAqB,EAAE;AAC5C,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAA,EAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA,EAAA,CAAI;QACvC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,CAAA,EAAA,CAAI;AAClC,QAAA,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM;QACzB,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,KAAK,CAAA,EAAA,CAAI;IACtC;AAGA,IAAA,UAAU,CAAC,KAAY,EAAA;AACrB,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE;AACpB,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAc;AACnC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa;AACvC,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY;QAClC,MAAM,UAAU,GAAG,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAChD,MAAM,UAAU,GAAG,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;AACxD,QAAA,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU;YAAE,IAAI,CAAC,SAAS,EAAE;IAClD;IAGA,KAAK,GAAA;QACH,IAAI,CAAC,SAAS,EAAE;IAClB;IAEA,UAAU,GAAA;AACR,QAAA,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE;YACjB,IAAI,CAAC,SAAS,EAAE;QAClB;aAAO;AACL,YAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,YAAA,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE;QAC3B;IACF;AAEA,IAAA,cAAc,CAAC,CAAe,EAAA;AAC5B,QAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACd,IAAI,CAAC,SAAS,EAAE;IAClB;AAEA,IAAA,IAAI,WAAW,GAAA;AACb,QAAA,OAAO,CAAA,iCAAA,EAAoC,IAAI,CAAC,OAAO,CAAA,kBAAA,EAAqB,IAAI,CAAC,IAAI,CAAA,wBAAA,EAA2B,IAAI,CAAC,YAAY,EAAE;IACrI;IAEA,KAAK,GAAA;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;AAC7C,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC;AAC9C,QAAA,IAAI,CAAC,YAAY,GAAG,IAAI;AACxB,QAAA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B;AAEA,IAAA,MAAM,CAAC,CAAe,EAAA;AACpB,QAAA,IAAI,CAAC,KAAK,IAAI,CAAC,YAAY;YAAE;AAC7B,QAAA,IAAI,CAAC,YAAY,GAAG,CAAC;AACrB,QAAA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1B;AAEA,IAAA,UAAU,CAAC,CAAe,EAAA;QACxB,OAAO,CAAC,KAAK,OAAO,GAAG,OAAO,GAAG,CAAC,KAAK,MAAM,GAAG,MAAM,GAAG,QAAQ;IACnE;AAEA,IAAA,QAAQ,CAAC,CAAe,EAAA;QACtB,OAAO,CAAC,KAAK,OAAO,GAAG,UAAU,GAAG,CAAC,KAAK,MAAM,GAAG,MAAM,GAAG,gBAAgB;IAC9E;uGA1HW,sBAAsB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAtB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,sBAAsB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,MAAA,EAAA,YAAA,EAAA,cAAA,EAAA,SAAA,EAAA,WAAA,EAAA,EAAA,OAAA,EAAA,EAAA,WAAA,EAAA,aAAA,EAAA,aAAA,EAAA,eAAA,EAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,gBAAA,EAAA,oBAAA,EAAA,yBAAA,EAAA,SAAA,EAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,aAAA,EAAA,EAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,QAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAAA,CAAA,MAAA,CAAA,EAAA,WAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAhSvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwET,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,gsJAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EA3ES,eAAe,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAmSd,sBAAsB,EAAA,UAAA,EAAA,CAAA;kBArSlC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,iBAAiB,EAAA,OAAA,EAClB,CAAC,eAAe,CAAC,mBACT,uBAAuB,CAAC,MAAM,EAAA,IAAA,EACzC,EAAE,SAAS,EAAE,aAAa,EAAE,EAAA,QAAA,EACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwET,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,gsJAAA,CAAA,EAAA;;sBAyNA;;sBACA;;sBACA;;sBACA;;sBAEA;;sBACA;;sBAMA,SAAS;uBAAC,MAAM;;sBAwDhB,YAAY;uBAAC,gBAAgB,EAAE,CAAC,QAAQ,CAAC;;sBAWzC,YAAY;uBAAC,yBAAyB;;;ACnazC;;AAEG;;;;"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanso-protocol/theme-toggle",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@angular/core": ">=21.0.0",
|
|
7
7
|
"@angular/common": ">=21.0.0",
|
|
8
|
-
"@kanso-protocol/core": ">=2.0.
|
|
8
|
+
"@kanso-protocol/core": ">=2.0.3"
|
|
9
9
|
},
|
|
10
10
|
"description": "Kanso Protocol — theme-toggle (pattern).",
|
|
11
11
|
"author": "GregNBlack",
|