From c3bc8f1c0ce6bee9dac39a66eda32baa7a7e4975 Mon Sep 17 00:00:00 2001 From: Sebastian Enns Date: Fri, 16 Oct 2026 01:12:39 +0200 Subject: [PATCH 1/4] fix(sushi): clean up lightbox and tooltip lifecycles --- .../internal/lightbox-launcher.service.ts | 18 ++++++- sushi/src/lib/lightbox/lightbox.component.ts | 44 +++++++++------- .../testing/lightbox-launcher.service.spec.ts | 21 ++++++-- .../testing/lightbox.component.spec.ts | 51 ++++++++++++++++--- .../tooltip/testing/tooltip.directive.spec.ts | 23 ++++++++- sushi/src/lib/tooltip/tooltip.directive.ts | 38 +++++++------- sushi/testing/test-utils.ts | 11 +++- 7 files changed, 153 insertions(+), 53 deletions(-) diff --git a/sushi/src/lib/lightbox/internal/lightbox-launcher.service.ts b/sushi/src/lib/lightbox/internal/lightbox-launcher.service.ts index 479e4983..51755fc0 100644 --- a/sushi/src/lib/lightbox/internal/lightbox-launcher.service.ts +++ b/sushi/src/lib/lightbox/internal/lightbox-launcher.service.ts @@ -1,5 +1,14 @@ -import { ApplicationRef, ComponentRef, createComponent, DOCUMENT, EnvironmentInjector, inject, Injectable } from '@angular/core'; -import { GalleryImage } from '../../gallery'; +import { + ApplicationRef, + type ComponentRef, + createComponent, + DOCUMENT, + DestroyRef, + EnvironmentInjector, + inject, + Injectable, +} from '@angular/core'; +import { type GalleryImage } from '../../gallery'; import { Lightbox } from '../lightbox.component'; interface LightboxLaunchOptions { @@ -15,8 +24,13 @@ export class LightboxLauncher { private readonly applicationRef: ApplicationRef = inject(ApplicationRef); private readonly environmentInjector: EnvironmentInjector = inject(EnvironmentInjector); private readonly document: Document = inject(DOCUMENT); + private readonly destroyRef: DestroyRef = inject(DestroyRef); private activeRef: ComponentRef | null = null; + public constructor() { + this.destroyRef.onDestroy((): void => this.destroyActive()); + } + public show(image: GalleryImage, options: LightboxLaunchOptions): void { this.destroyActive(); const componentRef: ComponentRef = createComponent(Lightbox, { diff --git a/sushi/src/lib/lightbox/lightbox.component.ts b/sushi/src/lib/lightbox/lightbox.component.ts index 45439c96..83f73a01 100644 --- a/sushi/src/lib/lightbox/lightbox.component.ts +++ b/sushi/src/lib/lightbox/lightbox.component.ts @@ -5,19 +5,20 @@ import { ChangeDetectionStrategy, Component, DestroyRef, + ErrorHandler, inject, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, NgZone, PLATFORM_ID, } from '@angular/core'; import type PhotoSwipe from 'photoswipe'; import type { SlideData } from 'photoswipe'; -import { GalleryImage } from '../gallery'; -import { LIGHTBOX_LOADER, LightboxLoader } from './internal/lightbox-loader.token'; +import { type GalleryImage } from '../gallery'; +import { LIGHTBOX_LOADER, type LightboxLoader } from './internal/lightbox-loader.token'; /** Opens Gallery images in a zoomable, gesture-enabled full-screen viewer powered by PhotoSwipe. */ @Component({ selector: 'sui-lightbox', exportAs: 'suiLightbox', template: '', changeDetection: ChangeDetectionStrategy.OnPush }) @@ -40,6 +41,7 @@ export class Lightbox { public readonly wheelToZoom: InputSignalWithTransform = input(false, { transform: booleanAttribute }); private readonly destroyRef: DestroyRef = inject(DestroyRef); + private readonly errorHandler: ErrorHandler = inject(ErrorHandler); private readonly zone: NgZone = inject(NgZone); private readonly platformId: object = inject(PLATFORM_ID); private readonly loadViewer: LightboxLoader = inject(LIGHTBOX_LOADER); @@ -79,19 +81,25 @@ export class Lightbox { private async launch(): Promise { this.launching = true; this.restoreTarget = document.activeElement instanceof HTMLElement ? document.activeElement : null; - const PhotoSwipeConstructor: typeof PhotoSwipe = await this.loadViewer(); - if (!this.open() || this.destroyRef.destroyed) { - this.launching = false; - return; - } + try { + const PhotoSwipeConstructor: typeof PhotoSwipe = await this.loadViewer(); + if (!this.open() || this.destroyRef.destroyed) return; - this.zone.runOutsideAngular((): void => { - const viewer: PhotoSwipe = this.createViewer(PhotoSwipeConstructor); - this.configureViewer(viewer); - this.instance = viewer; + this.zone.runOutsideAngular((): void => { + const viewer: PhotoSwipe = this.createViewer(PhotoSwipeConstructor); + this.configureViewer(viewer); + this.instance = viewer; + viewer.init(); + }); + } catch (error: unknown) { + if (this.destroyRef.destroyed) return; + this.open.set(false); + this.destroy(); + this.restoreFocus(); + this.errorHandler.handleError(error); + } finally { this.launching = false; - viewer.init(); - }); + } } private createViewer(PhotoSwipeConstructor: typeof PhotoSwipe): PhotoSwipe { @@ -148,8 +156,8 @@ export class Lightbox { appendTo: 'root', onInit: (element: HTMLElement, pswp: PhotoSwipe): void => { const update: () => void = (): void => { - const image: I = images[pswp.currIndex]; - element.textContent = grouped ? (image.caption ?? '') : (this.description() ?? image.caption ?? ''); + const image: I | undefined = images[pswp.currIndex]; + element.textContent = grouped ? (image?.caption ?? '') : (this.description() ?? image?.caption ?? ''); element.hidden = element.textContent.length === 0; }; pswp.on('change', update); diff --git a/sushi/src/lib/lightbox/testing/lightbox-launcher.service.spec.ts b/sushi/src/lib/lightbox/testing/lightbox-launcher.service.spec.ts index 3eb38ea6..4a3461ba 100644 --- a/sushi/src/lib/lightbox/testing/lightbox-launcher.service.spec.ts +++ b/sushi/src/lib/lightbox/testing/lightbox-launcher.service.spec.ts @@ -1,10 +1,10 @@ -import { ApplicationRef } from '@angular/core'; +import { ApplicationRef, createEnvironmentInjector, EnvironmentInjector } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import type PhotoSwipe from 'photoswipe'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { GalleryImage } from '../../gallery'; +import { type GalleryImage } from '../../gallery'; import { LightboxLauncher } from '../internal/lightbox-launcher.service'; -import { LIGHTBOX_LOADER, LightboxLoader } from '../internal/lightbox-loader.token'; +import { LIGHTBOX_LOADER, type LightboxLoader } from '../internal/lightbox-loader.token'; class PhotoSwipeStub { public readonly element: HTMLElement = document.createElement('div'); @@ -54,3 +54,18 @@ describe('Lightbox launcher', (): void => { expect(document.querySelector('sui-lightbox')).not.toBe(first); }); }); + +describe('Lightbox launcher lifecycle', (): void => { + it('removes its dynamic host when the owning injector is destroyed', (): void => { + TestBed.configureTestingModule({ providers: [{ provide: LIGHTBOX_LOADER, useValue: loadViewer }] }); + const injector: EnvironmentInjector = createEnvironmentInjector([LightboxLauncher], TestBed.inject(EnvironmentInjector)); + const launcher: LightboxLauncher = injector.get(LightboxLauncher); + launcher.show(image, { zoom: true, wheelToZoom: false, ariaLabel: 'Menu image', description: null }); + + expect(document.querySelectorAll('sui-lightbox')).toHaveLength(1); + + injector.destroy(); + + expect(document.querySelector('sui-lightbox')).toBeNull(); + }); +}); diff --git a/sushi/src/lib/lightbox/testing/lightbox.component.spec.ts b/sushi/src/lib/lightbox/testing/lightbox.component.spec.ts index 543839ae..9b0de2b0 100644 --- a/sushi/src/lib/lightbox/testing/lightbox.component.spec.ts +++ b/sushi/src/lib/lightbox/testing/lightbox.component.spec.ts @@ -1,12 +1,12 @@ -import { Component, DebugElement, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, ErrorHandler, type DebugElement, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import type PhotoSwipe from 'photoswipe'; import type { PhotoSwipeOptions } from 'photoswipe'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { render } from '../../../../testing/test-utils'; -import { GalleryImage } from '../../gallery'; -import { LIGHTBOX_LOADER, LightboxLoader } from '../internal/lightbox-loader.token'; +import { render, requireItem } from '../../../../testing/test-utils'; +import { type GalleryImage } from '../../gallery'; +import { LIGHTBOX_LOADER, type LightboxLoader } from '../internal/lightbox-loader.token'; import { LightboxLauncher } from '../internal/lightbox-launcher.service'; import { LightboxTrigger } from '../lightbox-trigger.directive'; import { Lightbox } from '../lightbox.component'; @@ -21,7 +21,7 @@ class PhotoSwipeStub { public readonly options: PhotoSwipeOptions; public currIndex: number; public currSlide: undefined; - private readonly handlers: Map void> = new Map void>(); + private readonly handlers: Map void> = new Map(); public constructor(options: PhotoSwipeOptions) { this.options = options; @@ -66,7 +66,7 @@ const loadViewer: LightboxLoader = (): Promise => class Host { public readonly activeIndex: WritableSignal = signal(1); public readonly open: WritableSignal = signal(false); - public readonly images: readonly GalleryImage[] = [ + public readonly images: readonly [GalleryImage, GalleryImage] = [ { value: 'a', src: 'a.jpg', width: 800, height: 600, alt: 'A' }, { value: 'b', src: 'b.jpg', width: 800, height: 600, alt: 'B' }, ]; @@ -124,6 +124,41 @@ describe('Lightbox', (): void => { }); }); +describe('Lightbox loading failures', (): void => { + it('reports a failed load, closes safely, and allows another opening attempt', async (): Promise => { + const failure: Error = new Error('Viewer download failed'); + const loader: ReturnType> = vi + .fn() + .mockRejectedValueOnce(failure) + .mockImplementationOnce(loadViewer); + const handleError: ReturnType = vi.fn(); + TestBed.overrideComponent(Host, { + set: { + providers: [ + { provide: LIGHTBOX_LOADER, useValue: loader }, + { provide: ErrorHandler, useValue: { handleError } }, + ], + }, + }); + const fixture: ComponentFixture = render(Host); + const instance: Lightbox = fixture.debugElement.query(By.directive(Lightbox)).componentInstance as Lightbox; + instance.show(); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(handleError).toHaveBeenCalledWith(failure); + expect(fixture.componentInstance.open()).toBe(false); + expect(PhotoSwipeStub.latest).toBeNull(); + + instance.show(); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(PhotoSwipeStub.latest).not.toBeNull(); + expect(fixture.componentInstance.open()).toBe(true); + }); +}); + describe('Lightbox focus management', (): void => { it('restores focus after the viewer is destroyed', async (): Promise => { const fixture: ComponentFixture = render(Host); @@ -151,7 +186,7 @@ describe('Lightbox trigger', (): void => { trigger.injector.get(LightboxTrigger).show(); - const [source, options] = launcher.show.mock.calls[0] as [ + const [source, options] = requireItem(launcher.show.mock.calls, 0) as [ GalleryImage, { ariaLabel: string; zoom: boolean; wheelToZoom: boolean }, ]; diff --git a/sushi/src/lib/tooltip/testing/tooltip.directive.spec.ts b/sushi/src/lib/tooltip/testing/tooltip.directive.spec.ts index 0adaf13b..eb49bdb9 100644 --- a/sushi/src/lib/tooltip/testing/tooltip.directive.spec.ts +++ b/sushi/src/lib/tooltip/testing/tooltip.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, DebugElement, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, type DebugElement, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { render } from '../../../../testing/test-utils'; @@ -80,3 +80,22 @@ describe('Tooltip', (): void => { expect(document.querySelector('[role="tooltip"]')).toBeNull(); }); }); + +describe('Tooltip description ownership', (): void => { + it('preserves existing descriptions when hiding before opening or hiding repeatedly', (): void => { + const fixture: ComponentFixture = render(Host); + const target: DebugElement = fixture.debugElement.query(By.directive(Tooltip)); + const tooltip: Tooltip = target.injector.get(Tooltip); + const button: HTMLButtonElement = target.nativeElement as HTMLButtonElement; + tooltip.hide(); + + expect(button.getAttribute('aria-describedby')).toBe('existing-description'); + + tooltip.show(); + fixture.detectChanges(); + tooltip.hide(); + tooltip.hide(); + + expect(button.getAttribute('aria-describedby')).toBe('existing-description'); + }); +}); diff --git a/sushi/src/lib/tooltip/tooltip.directive.ts b/sushi/src/lib/tooltip/tooltip.directive.ts index dc317882..5a06e642 100644 --- a/sushi/src/lib/tooltip/tooltip.directive.ts +++ b/sushi/src/lib/tooltip/tooltip.directive.ts @@ -1,34 +1,34 @@ import { - ConnectedOverlayPositionChange, - ConnectedPosition, - FlexibleConnectedPositionStrategy, + type ConnectedOverlayPositionChange, + type ConnectedPosition, + type FlexibleConnectedPositionStrategy, Overlay, OverlayPositionBuilder, - OverlayRef, + type OverlayRef, } from '@angular/cdk/overlay'; import { ComponentPortal } from '@angular/cdk/portal'; import { booleanAttribute, - ComponentRef, + type ComponentRef, DestroyRef, Directive, ElementRef, inject, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, numberAttribute, } from '@angular/core'; import { TooltipSurface } from './internal/tooltip-surface.component'; -import { TooltipPlacement } from './tooltip.interfaces'; +import { type TooltipPlacement } from './tooltip.interfaces'; let nextTooltipId: number = 0; -const POSITIONS: Record = { - top: [{ originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -1 }], - right: [{ originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 1 }], - bottom: [{ originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 1 }], - left: [{ originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -1 }], +const POSITIONS: Record = { + top: { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -1 }, + right: { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 1 }, + bottom: { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 1 }, + left: { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -1 }, }; const PLACEMENTS: readonly TooltipPlacement[] = ['top', 'right', 'bottom', 'left']; @@ -41,7 +41,7 @@ function resolvePlacement(position: ConnectedPosition): TooltipPlacement { } function createPosition(placement: TooltipPlacement, offset: number): ConnectedPosition { - const position: ConnectedPosition = POSITIONS[placement][0]; + const position: ConnectedPosition = POSITIONS[placement]; return { ...position, offsetX: position.offsetX ? Math.sign(position.offsetX) * offset : undefined, @@ -86,7 +86,7 @@ export class Tooltip { private resolvedPlacement: TooltipPlacement = 'top'; private showTimer: ReturnType | null = null; private hideTimer: ReturnType | null = null; - private previousDescription: string | null = null; + private previousDescription: string | null | undefined; public constructor() { this.destroyRef.onDestroy((): void => this.destroy()); @@ -158,10 +158,12 @@ export class Tooltip { } private restoreDescription(): void { + if (this.previousDescription === undefined) return; + const target: HTMLElement = this.element.nativeElement; - if (this.previousDescription) target.setAttribute('aria-describedby', this.previousDescription); - else target.removeAttribute('aria-describedby'); - this.previousDescription = null; + if (this.previousDescription === null) target.removeAttribute('aria-describedby'); + else target.setAttribute('aria-describedby', this.previousDescription); + this.previousDescription = undefined; } private clearTimers(): void { diff --git a/sushi/testing/test-utils.ts b/sushi/testing/test-utils.ts index 747e1fe7..c34f21cb 100644 --- a/sushi/testing/test-utils.ts +++ b/sushi/testing/test-utils.ts @@ -1,5 +1,5 @@ -import { Type } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { type Type } from '@angular/core'; +import { type ComponentFixture, TestBed } from '@angular/core/testing'; /** Creates and initially renders a standalone test host. */ export function render(host: Type): ComponentFixture { @@ -43,3 +43,10 @@ export function press(target: EventTarget, key: string, init: KeyboardEventInit target.dispatchEvent(event); return event; } + +/** Returns a fixture item or fails with its missing index instead of an unchecked assertion. */ +export function requireItem(items: ArrayLike, index: number): T { + const item: T | undefined = items[index]; + if (item === undefined) throw new Error(`Expected a fixture item at index ${index}.`); + return item; +} From a12edc28c10b81275f00714fea2f727401a54797 Mon Sep 17 00:00:00 2001 From: Sebastian Enns Date: Fri, 16 Oct 2026 01:12:39 +0200 Subject: [PATCH 2/4] refactor(sushi): align code with TypeScript conventions --- .prettierrc | 2 +- e2e/specs/accordion.spec.ts | 4 +- e2e/specs/autocomplete.spec.ts | 2 +- e2e/specs/button.spec.ts | 2 +- e2e/specs/chip.spec.ts | 4 +- e2e/specs/dialog-content-resize.spec.ts | 4 +- e2e/specs/dialog-position.spec.ts | 2 +- e2e/specs/dialog.spec.ts | 4 +- e2e/specs/divider.spec.ts | 2 +- e2e/specs/drawer.spec.ts | 2 +- e2e/specs/file-drop.spec.ts | 2 +- e2e/specs/focus.spec.ts | 2 +- e2e/specs/gallery.spec.ts | 2 +- e2e/specs/listbox.spec.ts | 2 +- e2e/specs/menu.spec.ts | 2 +- e2e/specs/mobile-overlay.spec.ts | 2 +- e2e/specs/multi-select.spec.ts | 2 +- e2e/specs/order-list.spec.ts | 2 +- e2e/specs/popover.spec.ts | 4 +- e2e/specs/responsive-layout.spec.ts | 2 +- e2e/specs/select-dialog.spec.ts | 2 +- e2e/specs/select.spec.ts | 4 +- e2e/specs/sidebar.spec.ts | 2 +- e2e/specs/tabs.spec.ts | 2 +- e2e/specs/toast.spec.ts | 4 +- e2e/specs/tooltip.spec.ts | 4 +- e2e/support/geometry.ts | 2 +- eslint.config.mjs | 4 ++ playground/src/app/app.config.ts | 2 +- playground/src/app/app.ts | 10 +-- .../src/app/pages/accordion/accordion.page.ts | 2 +- .../examples/actions/actions.example.ts | 8 +-- .../accordion/examples/basic/basic.example.ts | 2 +- .../examples/control/control.example.ts | 2 +- .../examples/templates/templates.example.ts | 8 ++- playground/src/app/pages/api/api.page.ts | 2 +- .../app/pages/auto-focus/auto-focus.page.ts | 2 +- .../examples/dynamic/dynamic.example.ts | 2 +- .../pages/autocomplete/autocomplete.page.ts | 2 +- .../examples/async/async.example.ts | 4 +- .../examples/basic/basic.example.ts | 6 +- .../examples/filtering/filtering.example.ts | 10 +-- .../examples/states/states.example.ts | 10 ++- .../src/app/pages/avatar/avatar.page.ts | 2 +- .../avatar/examples/usage/usage.example.ts | 2 +- playground/src/app/pages/badge/badge.page.ts | 2 +- .../badge/examples/context/context.example.ts | 2 +- .../badge/examples/usage/usage.example.ts | 2 +- .../app/pages/breadcrumb/breadcrumb.page.ts | 2 +- .../examples/usage/usage.example.ts | 2 +- .../src/app/pages/button/button.page.ts | 2 +- .../examples/appearance/appearance.example.ts | 2 +- .../button/examples/states/states.example.ts | 2 +- playground/src/app/pages/card/card.page.ts | 2 +- .../examples/appearance/appearance.example.ts | 2 +- .../src/app/pages/checkbox/checkbox.page.ts | 2 +- .../checkbox/examples/usage/usage.example.ts | 4 +- playground/src/app/pages/chip/chip.page.ts | 2 +- .../chip/examples/removal/removal.example.ts | 2 +- playground/src/app/pages/code/code.page.ts | 2 +- .../pages/color-picker/color-picker.page.ts | 2 +- .../examples/basic/basic.example.ts | 4 +- .../examples/presets/presets.example.ts | 2 +- .../src/app/pages/data-view/data-view.page.ts | 2 +- .../examples/infinite/infinite.example.ts | 28 +++++++- .../examples/layout/layout.example.ts | 4 +- .../examples/remote/remote.example.ts | 21 +++++- .../src/app/pages/dialog/dialog.page.ts | 2 +- .../content-resize/content-resize.example.ts | 2 +- .../coordinates/coordinates.example.ts | 4 +- .../examples/dynamic/dynamic.example.ts | 2 +- .../dialog/examples/form/form.example.ts | 8 +-- .../src/app/pages/divider/divider.page.ts | 2 +- .../src/app/pages/drawer/drawer.page.ts | 2 +- .../drawer/examples/cart/cart.example.ts | 2 +- .../examples/controlled/controlled.example.ts | 2 +- .../examples/toggles/toggles.example.ts | 2 +- .../src/app/pages/fieldset/fieldset.page.ts | 2 +- .../file-drop/examples/basic/basic.example.ts | 6 +- .../examples/custom/custom.example.ts | 4 +- .../src/app/pages/file-drop/file-drop.page.ts | 2 +- .../examples/native/native.example.ts | 10 ++- .../examples/options/options.example.ts | 10 ++- .../app/pages/file-input/file-input.page.ts | 2 +- .../gallery/examples/usage/usage.example.ts | 10 ++- .../src/app/pages/gallery/gallery.page.ts | 2 +- playground/src/app/pages/icon/icon.page.ts | 2 +- .../examples/positions/positions.example.ts | 8 ++- .../src/app/pages/indicator/indicator.page.ts | 2 +- .../examples/commerce/commerce.example.ts | 2 +- .../app/pages/input-group/input-group.page.ts | 2 +- .../examples/basic/basic.example.ts | 4 +- .../pages/input-number/input-number.page.ts | 2 +- .../input-otp/examples/basic/basic.example.ts | 4 +- .../src/app/pages/input-otp/input-otp.page.ts | 2 +- .../examples/search/search.example.ts | 4 +- .../pages/input-surface/input-surface.page.ts | 2 +- .../examples/appearance/appearance.example.ts | 2 +- .../input/examples/basic/basic.example.ts | 4 +- playground/src/app/pages/input/input.page.ts | 2 +- .../join/examples/nested/nested.example.ts | 2 +- playground/src/app/pages/join/join.page.ts | 2 +- .../pages/kbd/examples/usage/usage.example.ts | 2 +- playground/src/app/pages/kbd/kbd.page.ts | 2 +- .../label/examples/basic/basic.example.ts | 2 +- playground/src/app/pages/label/label.page.ts | 2 +- .../examples/source/source.example.ts | 2 +- .../src/app/pages/lightbox/lightbox.page.ts | 2 +- .../examples/activity/activity.example.ts | 2 +- .../list/examples/tasks/tasks.example.ts | 2 +- playground/src/app/pages/list/list.page.ts | 2 +- .../listbox/examples/basic/basic.example.ts | 6 +- .../listbox/examples/lazy/lazy.example.ts | 14 +++- .../examples/multiple/multiple.example.ts | 6 +- .../listbox/examples/states/states.example.ts | 2 +- .../examples/templates/templates.example.ts | 8 +-- .../src/app/pages/listbox/listbox.page.ts | 2 +- .../mask/examples/gallery/gallery.example.ts | 2 +- playground/src/app/pages/mask/mask.page.ts | 2 +- .../menu/examples/active/active.example.ts | 4 +- .../menu/examples/basic/basic.example.ts | 4 +- .../menu/examples/context/context.example.ts | 4 +- .../menu/examples/groups/groups.example.ts | 8 +-- .../menu/examples/popup/popup.example.ts | 4 +- .../menu/examples/states/states.example.ts | 2 +- .../menu/examples/submenu/submenu.example.ts | 4 +- playground/src/app/pages/menu/menu.page.ts | 2 +- .../src/app/pages/message/message.page.ts | 2 +- .../examples/basic/basic.example.ts | 6 +- .../examples/behavior/behavior.example.ts | 4 +- .../category-selection.example.ts | 8 +-- .../examples/groups/groups.example.ts | 10 +-- .../examples/selected/selected.example.ts | 10 +-- .../examples/states/states.example.ts | 11 ++- .../examples/templates/templates.example.ts | 6 +- .../pages/multi-select/multi-select.page.ts | 2 +- .../navbar/examples/basic/basic.example.ts | 4 +- .../navbar/examples/custom/custom.example.ts | 4 +- .../examples/desktop/desktop.example.ts | 6 +- .../orientations/orientations.example.ts | 4 +- .../examples/submenus/submenus.example.ts | 4 +- .../src/app/pages/navbar/navbar.page.ts | 2 +- .../examples/basic/basic.example.ts | 6 +- .../examples/drag-drop/drag-drop.example.ts | 4 +- .../examples/states/states.example.ts | 4 +- .../examples/templates/templates.example.ts | 6 +- .../app/pages/order-list/order-list.page.ts | 2 +- .../examples/invite/invite.example.ts | 6 +- .../src/app/pages/overview/overview.page.ts | 4 +- .../examples/appearance/appearance.example.ts | 2 +- .../examples/basic/basic.example.ts | 2 +- .../examples/compact/compact.example.ts | 4 +- .../app/pages/pagination/pagination.page.ts | 2 +- .../examples/selection/selection.example.ts | 2 +- .../src/app/pages/popover/popover.page.ts | 2 +- .../examples/animations/animations.example.ts | 2 +- .../progress/examples/usage/usage.example.ts | 2 +- .../src/app/pages/progress/progress.page.ts | 2 +- .../radio/examples/usage/usage.example.ts | 4 +- playground/src/app/pages/radio/radio.page.ts | 2 +- .../range/examples/basic/basic.example.ts | 4 +- .../range/examples/mixer/mixer.example.ts | 4 +- playground/src/app/pages/range/range.page.ts | 2 +- .../examples/basic/basic.example.ts | 6 +- .../examples/layout/layout.example.ts | 4 +- .../examples/template/template.example.ts | 4 +- .../pages/select-button/select-button.page.ts | 2 +- .../select/examples/basic/basic.example.ts | 6 +- .../examples/behavior/behavior.example.ts | 4 +- .../examples/options/options.example.ts | 10 +-- .../select/examples/states/states.example.ts | 4 +- .../examples/templates/templates.example.ts | 6 +- .../src/app/pages/select/select.page.ts | 2 +- .../examples/workspace/workspace.example.ts | 6 +- .../src/app/pages/sidebar/sidebar.page.ts | 2 +- .../src/app/pages/skeleton/skeleton.page.ts | 2 +- .../examples/contexts/contexts.example.ts | 2 +- .../spinner/examples/usage/usage.example.ts | 2 +- .../src/app/pages/spinner/spinner.page.ts | 2 +- .../examples/services/services.example.ts | 11 ++- .../src/app/pages/status/status.page.ts | 2 +- .../src/app/pages/styling/styling.page.ts | 6 +- .../examples/appearance/appearance.example.ts | 2 +- .../table/examples/basic/basic.example.ts | 6 +- .../table/examples/mobile/mobile.example.ts | 2 +- .../table/examples/simple/simple.example.ts | 2 +- .../table/examples/states/states.example.ts | 4 +- .../examples/templates/templates.example.ts | 2 +- playground/src/app/pages/table/table.page.ts | 2 +- .../tabs/examples/basic/basic.example.ts | 4 +- .../examples/behavior/behavior.example.ts | 4 +- .../examples/variants/variants.example.ts | 4 +- playground/src/app/pages/tabs/tabs.page.ts | 2 +- .../examples/appearance/appearance.example.ts | 2 +- .../textarea/examples/basic/basic.example.ts | 4 +- .../examples/resize/resize.example.ts | 2 +- .../src/app/pages/textarea/textarea.page.ts | 2 +- playground/src/app/pages/toast/toast.page.ts | 2 +- .../examples/usage/usage.example.ts | 4 +- .../pages/toggle-button/toggle-button.page.ts | 2 +- .../toggle/examples/usage/usage.example.ts | 4 +- .../src/app/pages/toggle/toggle.page.ts | 2 +- .../src/app/pages/tooltip/tooltip.page.ts | 2 +- .../api-reference/api-reference.component.ts | 6 +- .../example-code/example-code.component.ts | 8 +-- .../example-code/example-highlighter.ts | 11 +-- .../app/shared/example-code/example-source.ts | 7 +- .../markdown-document.component.ts | 10 +-- playground/src/package-styles.css | 8 +-- playground/src/playground.source.css | 6 +- .../lib/accordion/accordion-item.directive.ts | 12 +++- .../src/lib/accordion/accordion.component.css | 6 +- .../src/lib/accordion/accordion.component.ts | 16 +++-- .../src/lib/accordion/accordion.templates.ts | 2 +- sushi/src/lib/accordion/index.ts | 2 +- .../accordion-actions.component.spec.ts | 16 ++--- .../testing/accordion.component.spec.ts | 36 +++++----- .../lib/auto-focus/auto-focus.directive.ts | 10 ++- .../testing/auto-focus.directive.spec.ts | 2 +- .../autocomplete/autocomplete.component.ts | 35 ++++++---- .../autocomplete/autocomplete.interfaces.ts | 4 +- .../autocomplete/autocomplete.templates.ts | 4 +- sushi/src/lib/autocomplete/index.ts | 2 +- .../testing/autocomplete.component.spec.ts | 4 +- sushi/src/lib/avatar/avatar.component.ts | 10 +-- sushi/src/lib/avatar/avatar.interfaces.ts | 4 +- sushi/src/lib/avatar/index.ts | 2 +- .../avatar/testing/avatar.component.spec.ts | 4 +- sushi/src/lib/badge/badge.directive.ts | 4 +- sushi/src/lib/badge/badge.interfaces.ts | 2 +- sushi/src/lib/badge/index.ts | 2 +- .../lib/badge/testing/badge.directive.spec.ts | 4 +- .../lib/breadcrumb/breadcrumb.component.css | 4 +- .../lib/breadcrumb/breadcrumb.component.ts | 12 +++- .../lib/breadcrumb/breadcrumb.templates.ts | 2 +- sushi/src/lib/breadcrumb/index.ts | 2 +- .../testing/breadcrumb.component.spec.ts | 4 +- sushi/src/lib/button/button.directive.ts | 8 +-- sushi/src/lib/button/button.interfaces.ts | 2 +- sushi/src/lib/button/index.ts | 2 +- .../button/testing/button.directive.spec.ts | 4 +- sushi/src/lib/card/card.component.ts | 4 +- sushi/src/lib/card/card.interfaces.ts | 2 +- sushi/src/lib/card/index.ts | 2 +- .../lib/card/testing/card.component.spec.ts | 4 +- sushi/src/lib/checkbox/checkbox.directive.ts | 2 +- .../testing/checkbox.directive.spec.ts | 4 +- sushi/src/lib/chip/chip.component.css | 2 +- sushi/src/lib/chip/chip.component.ts | 12 ++-- sushi/src/lib/chip/chip.interfaces.ts | 4 +- sushi/src/lib/chip/chip.templates.ts | 2 +- sushi/src/lib/chip/index.ts | 2 +- .../lib/chip/testing/chip.component.spec.ts | 4 +- sushi/src/lib/code/code-line.directive.ts | 2 +- sushi/src/lib/code/code.component.ts | 12 ++-- sushi/src/lib/code/code.templates.ts | 2 +- sushi/src/lib/code/index.ts | 2 +- .../lib/code/testing/code.component.spec.ts | 4 +- .../color-picker/color-picker.component.css | 10 +-- .../color-picker/color-picker.component.ts | 48 ++++++++----- .../color-picker/color-picker.templates.ts | 2 +- sushi/src/lib/color-picker/index.ts | 2 +- .../testing/color-picker.component.spec.ts | 14 ++-- .../src/lib/data-view/data-view.component.ts | 26 +++---- .../src/lib/data-view/data-view.templates.ts | 2 +- sushi/src/lib/data-view/index.ts | 2 +- .../testing/data-view.component.spec.ts | 8 +-- .../src/lib/dialog/dialog-close.directive.ts | 4 +- .../src/lib/dialog/dialog-header.directive.ts | 4 +- .../lib/dialog/dialog-trigger.directive.ts | 12 +++- sushi/src/lib/dialog/dialog.component.ts | 18 ++--- sushi/src/lib/dialog/index.ts | 2 +- .../dialog/testing/dialog.component.spec.ts | 8 +-- sushi/src/lib/divider/divider.directive.ts | 4 +- sushi/src/lib/divider/divider.interfaces.ts | 2 +- sushi/src/lib/divider/index.ts | 2 +- .../divider/testing/divider.directive.spec.ts | 2 +- .../src/lib/drawer/drawer-close.directive.ts | 4 +- .../lib/drawer/drawer-trigger.directive.ts | 12 +++- sushi/src/lib/drawer/drawer.component.ts | 16 ++--- sushi/src/lib/drawer/index.ts | 2 +- .../drawer/testing/drawer.component.spec.ts | 6 +- sushi/src/lib/fieldset/fieldset.directive.ts | 2 +- .../testing/fieldset.directive.spec.ts | 4 +- .../src/lib/file-input/file-drop.component.ts | 20 +++--- .../src/lib/file-input/file-drop.templates.ts | 2 +- .../lib/file-input/file-input.interfaces.ts | 2 +- sushi/src/lib/file-input/index.ts | 4 +- .../testing/file-drop.component.spec.ts | 10 +-- .../testing/file-input.directive.spec.ts | 4 +- .../lib/form-control/fluid-control.state.ts | 2 +- .../lib/form-control/form-control-state.ts | 2 +- .../form-control/form-control.interfaces.ts | 2 +- sushi/src/lib/form-control/index.ts | 2 +- .../form-control/selection-control.state.ts | 4 +- sushi/src/lib/gallery/gallery.component.css | 2 +- sushi/src/lib/gallery/gallery.component.ts | 17 ++--- sushi/src/lib/gallery/index.ts | 2 +- .../gallery/testing/gallery.component.spec.ts | 16 ++--- sushi/src/lib/indicator/index.ts | 2 +- .../lib/indicator/indicator-item.directive.ts | 4 +- .../testing/indicator.directive.spec.ts | 2 +- .../lib/input-group/input-group.directive.ts | 4 +- .../testing/input-group.directive.spec.ts | 6 +- sushi/src/lib/input-number/index.ts | 2 +- .../input-number/input-number.component.ts | 24 ++++--- .../input-number/input-number.interfaces.ts | 2 +- .../input-number/input-number.templates.ts | 2 +- .../testing/input-number.component.spec.ts | 18 ++--- .../src/lib/input-otp/input-otp.component.ts | 20 +++--- .../testing/input-otp.component.spec.ts | 4 +- .../input-surface-control.directive.ts | 2 +- .../input-surface/input-surface.directive.ts | 4 +- .../testing/input-surface.directive.spec.ts | 4 +- sushi/src/lib/input/index.ts | 2 +- sushi/src/lib/input/input.directive.ts | 2 +- sushi/src/lib/input/input.interfaces.ts | 2 +- .../lib/input/testing/input.directive.spec.ts | 4 +- sushi/src/lib/join/index.ts | 2 +- sushi/src/lib/join/join.directive.ts | 4 +- sushi/src/lib/join/join.interfaces.ts | 2 +- .../lib/join/testing/join.directive.spec.ts | 2 +- sushi/src/lib/kbd/index.ts | 2 +- sushi/src/lib/kbd/kbd.directive.ts | 4 +- sushi/src/lib/kbd/kbd.interfaces.ts | 2 +- .../src/lib/kbd/testing/kbd.directive.spec.ts | 2 +- sushi/src/lib/label/label.directive.ts | 2 +- .../lib/label/testing/label.directive.spec.ts | 4 +- .../internal/lightbox-loader.token.ts | 2 +- .../lightbox/lightbox-trigger.directive.ts | 12 +++- sushi/src/lib/list/index.ts | 2 +- sushi/src/lib/list/list.component.ts | 8 +-- sushi/src/lib/list/list.interfaces.ts | 2 +- sushi/src/lib/list/list.templates.ts | 2 +- .../lib/list/testing/list.component.spec.ts | 10 +-- sushi/src/lib/listbox/index.ts | 2 +- sushi/src/lib/listbox/listbox.component.ts | 38 +++++----- sushi/src/lib/listbox/listbox.interfaces.ts | 4 +- sushi/src/lib/listbox/listbox.templates.ts | 2 +- .../listbox/testing/listbox.component.spec.ts | 6 +- sushi/src/lib/mask/index.ts | 2 +- sushi/src/lib/mask/mask.directive.ts | 4 +- .../lib/mask/testing/mask.directive.spec.ts | 6 +- .../menu/context-menu-trigger.directive.ts | 12 +++- sushi/src/lib/menu/index.ts | 2 +- .../menu/internal/menu-action.component.css | 8 +-- .../menu/internal/menu-action.component.ts | 8 +-- .../menu/internal/menu-level.component.css | 16 ++--- .../lib/menu/internal/menu-level.component.ts | 30 ++++---- .../menu/internal/menu-surface.component.ts | 17 +++-- sushi/src/lib/menu/menu-trigger.directive.ts | 14 +++- sushi/src/lib/menu/menu.component.ts | 22 ++++-- sushi/src/lib/menu/menu.interfaces.ts | 2 +- sushi/src/lib/menu/menu.templates.ts | 4 +- .../lib/menu/testing/menu.component.spec.ts | 4 +- sushi/src/lib/message/index.ts | 2 +- sushi/src/lib/message/message.directive.ts | 4 +- sushi/src/lib/message/message.interfaces.ts | 2 +- .../message/testing/message.directive.spec.ts | 2 +- .../multi-select/multi-select.component.ts | 45 ++++++------ .../multi-select/multi-select.interfaces.ts | 14 ++-- .../multi-select/multi-select.templates.ts | 14 ++-- .../testing/multi-select.component.spec.ts | 4 +- sushi/src/lib/navbar/index.ts | 2 +- .../navbar/internal/navbar-links.directive.ts | 4 +- .../navbar/internal/navbar-menu.component.ts | 8 +-- sushi/src/lib/navbar/internal/navbar.state.ts | 2 +- .../navbar/navbar-item-template.directive.ts | 4 +- sushi/src/lib/navbar/navbar.component.ts | 26 +++---- sushi/src/lib/navbar/navbar.interfaces.ts | 2 +- .../navbar/testing/navbar.component.spec.ts | 12 ++-- sushi/src/lib/order-list/index.ts | 2 +- .../lib/order-list/order-list.component.css | 2 +- .../lib/order-list/order-list.component.ts | 44 ++++++++---- .../lib/order-list/order-list.interfaces.ts | 4 +- .../lib/order-list/order-list.templates.ts | 4 +- .../testing/order-list.component.spec.ts | 6 +- sushi/src/lib/pagination/index.ts | 2 +- .../lib/pagination/pagination.component.css | 2 +- .../lib/pagination/pagination.component.ts | 10 +-- .../lib/pagination/pagination.interfaces.ts | 2 +- .../lib/pagination/pagination.templates.ts | 6 +- .../testing/pagination.component.spec.ts | 4 +- sushi/src/lib/popover/index.ts | 2 +- .../lib/popover/popover-close.directive.ts | 4 +- .../lib/popover/popover-trigger.directive.ts | 12 +++- sushi/src/lib/popover/popover.component.css | 10 +-- sushi/src/lib/popover/popover.component.ts | 20 +++--- .../popover/testing/popover.component.spec.ts | 4 +- sushi/src/lib/progress/progress.directive.ts | 4 +- sushi/src/lib/progress/progress.interfaces.ts | 2 +- .../testing/progress.directive.spec.ts | 2 +- .../lib/radio/testing/radio.directive.spec.ts | 10 +-- sushi/src/lib/range/range.directive.ts | 2 +- .../lib/range/testing/range.directive.spec.ts | 4 +- sushi/src/lib/select-button/index.ts | 2 +- .../select-button/select-button.component.ts | 33 +++++---- .../select-button/select-button.interfaces.ts | 6 +- .../select-button/select-button.templates.ts | 2 +- .../testing/select-button.component.spec.ts | 28 ++++---- sushi/src/lib/select/index.ts | 2 +- sushi/src/lib/select/select.component.ts | 38 +++++----- sushi/src/lib/select/select.interfaces.ts | 4 +- sushi/src/lib/select/select.templates.ts | 7 +- .../testing/select-filter.component.spec.ts | 6 +- .../select/testing/select.component.spec.ts | 12 ++-- sushi/src/lib/selection/index.ts | 2 +- sushi/src/lib/selection/selection-overlay.ts | 2 +- .../src/lib/selection/selection.interfaces.ts | 2 +- sushi/src/lib/selection/selection.utils.ts | 4 +- sushi/src/lib/sidebar/index.ts | 2 +- .../sidebar-group-template.directive.ts | 6 +- .../sidebar-item-template.directive.ts | 6 +- sushi/src/lib/sidebar/sidebar.component.ts | 10 +-- sushi/src/lib/sidebar/sidebar.interfaces.ts | 2 +- .../sidebar/testing/sidebar.component.spec.ts | 8 +-- sushi/src/lib/skeleton/index.ts | 2 +- sushi/src/lib/skeleton/skeleton.directive.ts | 4 +- .../testing/skeleton.directive.spec.ts | 4 +- sushi/src/lib/spinner/index.ts | 2 +- sushi/src/lib/spinner/spinner.directive.ts | 4 +- sushi/src/lib/spinner/spinner.interfaces.ts | 2 +- .../spinner/testing/spinner.directive.spec.ts | 2 +- sushi/src/lib/status/index.ts | 2 +- sushi/src/lib/status/status.directive.ts | 4 +- sushi/src/lib/status/status.interfaces.ts | 2 +- .../status/testing/status.directive.spec.ts | 2 +- sushi/src/lib/table/index.ts | 2 +- sushi/src/lib/table/table.component.ts | 26 +++---- sushi/src/lib/table/table.interfaces.ts | 2 +- sushi/src/lib/table/table.templates.ts | 7 +- .../lib/table/testing/table.component.spec.ts | 18 ++--- sushi/src/lib/tabs/index.ts | 2 +- sushi/src/lib/tabs/tab.directive.ts | 12 +++- sushi/src/lib/tabs/tabs.component.css | 2 +- sushi/src/lib/tabs/tabs.component.ts | 17 +++-- sushi/src/lib/tabs/tabs.interfaces.ts | 2 +- .../lib/tabs/testing/tabs.component.spec.ts | 62 ++++++++-------- sushi/src/lib/textarea/index.ts | 2 +- .../testing/textarea.directive.spec.ts | 4 +- sushi/src/lib/textarea/textarea.directive.ts | 5 +- sushi/src/lib/textarea/textarea.interfaces.ts | 2 +- .../lib/toast/testing/toast.component.spec.ts | 6 +- sushi/src/lib/toast/toast.component.css | 6 +- sushi/src/lib/toast/toast.component.ts | 20 +++--- sushi/src/lib/toast/toast.interfaces.ts | 2 +- sushi/src/lib/toast/toast.service.ts | 9 ++- sushi/src/lib/toast/toast.templates.ts | 4 +- sushi/src/lib/toggle-button/index.ts | 2 +- .../testing/toggle-button.component.spec.ts | 4 +- .../toggle-button/toggle-button.component.ts | 22 +++--- .../toggle-button/toggle-button.interfaces.ts | 2 +- .../toggle-button/toggle-button.templates.ts | 2 +- .../toggle/testing/toggle.directive.spec.ts | 4 +- sushi/src/lib/tooltip/index.ts | 2 +- .../internal/tooltip-surface.component.ts | 4 +- sushi/src/styles/features/dialog.styles.css | 66 ++++++++--------- sushi/src/styles/features/fieldset.styles.css | 2 +- sushi/src/styles/features/input.styles.css | 2 +- sushi/src/styles/features/join.styles.css | 4 +- sushi/src/styles/features/lightbox.styles.css | 2 +- sushi/src/styles/features/navbar.styles.css | 70 +++++++++---------- .../styles/features/select-button.styles.css | 2 +- .../src/styles/features/selection.styles.css | 22 +++--- sushi/src/styles/features/sidebar.styles.css | 4 +- sushi/src/styles/features/tabs.styles.css | 2 +- sushi/src/styles/features/tooltip.styles.css | 10 +-- sushi/src/styles/sushi.core.css | 50 ++++++------- sushi/src/styles/sushi.source.css | 8 +-- sushi/src/styles/sushi.themes.css | 4 +- sushi/testing/setup.ts | 2 +- tests/package-install/src/main.ts | 3 +- tests/package-install/src/styles.css | 2 +- tests/package-install/tsconfig.json | 1 + tools/api-reference/generated-file.ts | 4 -- tools/api-reference/generator.spec.ts | 16 +++-- tools/api-reference/generator.ts | 23 ++---- tools/api-reference/source-reader.ts | 27 +++---- tools/generate-third-party-notices.ts | 13 ++-- tools/theme-reference.ts | 2 +- tsconfig.json | 1 + 481 files changed, 1593 insertions(+), 1312 deletions(-) diff --git a/.prettierrc b/.prettierrc index 8a4f6dcd..3f77d01f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -37,7 +37,7 @@ "files": ["*.scss", "*.css"], "options": { "printWidth": 200, - "singleQuote": true + "singleQuote": false } }, { diff --git a/e2e/specs/accordion.spec.ts b/e2e/specs/accordion.spec.ts index 3fe2a9f2..aa3305ff 100644 --- a/e2e/specs/accordion.spec.ts +++ b/e2e/specs/accordion.spec.ts @@ -1,5 +1,5 @@ -import { expect, Locator, Page, test } from '@playwright/test'; -import { Bounds, visibleBounds } from '../support/geometry'; +import { expect, type Locator, type Page, test } from '@playwright/test'; +import { type Bounds, visibleBounds } from '../support/geometry'; test('Accordion keyboard focus follows the shared focus tokens @css', async ({ page }: { page: Page }): Promise => { await page.goto('/accordion'); diff --git a/e2e/specs/autocomplete.spec.ts b/e2e/specs/autocomplete.spec.ts index c3dc913b..64eabfa9 100644 --- a/e2e/specs/autocomplete.spec.ts +++ b/e2e/specs/autocomplete.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; test('Autocomplete keeps keyboard focus while choosing a suggestion', async ({ page }: { page: Page }): Promise => { await page.goto('/autocomplete'); diff --git a/e2e/specs/button.spec.ts b/e2e/specs/button.spec.ts index b475eb47..f42eaa7d 100644 --- a/e2e/specs/button.spec.ts +++ b/e2e/specs/button.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; test( 'enabled Button actions use a pointer while disabled and loading actions do not', diff --git a/e2e/specs/chip.spec.ts b/e2e/specs/chip.spec.ts index 22d89dd0..5b74bf55 100644 --- a/e2e/specs/chip.spec.ts +++ b/e2e/specs/chip.spec.ts @@ -1,6 +1,6 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; -import { Bounds, visibleBounds } from '../support/geometry'; +import { type Bounds, visibleBounds } from '../support/geometry'; test('Chip sizes produce different surface dimensions and accept a public radius override', async ({ page, diff --git a/e2e/specs/dialog-content-resize.spec.ts b/e2e/specs/dialog-content-resize.spec.ts index d6c08ffc..926d98e1 100644 --- a/e2e/specs/dialog-content-resize.spec.ts +++ b/e2e/specs/dialog-content-resize.spec.ts @@ -1,5 +1,5 @@ -import { expect, Locator, Page, test } from '@playwright/test'; -import { Bounds, visibleBounds } from '../support/geometry'; +import { expect, type Locator, type Page, test } from '@playwright/test'; +import { type Bounds, visibleBounds } from '../support/geometry'; interface DialogBounds { readonly top: number; diff --git a/e2e/specs/dialog-position.spec.ts b/e2e/specs/dialog-position.spec.ts index f2a25d7a..602a0a4a 100644 --- a/e2e/specs/dialog-position.spec.ts +++ b/e2e/specs/dialog-position.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; interface Geometry { readonly left: number; diff --git a/e2e/specs/dialog.spec.ts b/e2e/specs/dialog.spec.ts index e8fe6fd4..604cf862 100644 --- a/e2e/specs/dialog.spec.ts +++ b/e2e/specs/dialog.spec.ts @@ -1,6 +1,6 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; -import { Bounds, visibleBounds } from '../support/geometry'; +import { type Bounds, visibleBounds } from '../support/geometry'; async function expectOrigin(dialog: Locator, origin: Bounds, tolerance: number): Promise { await expect(async (): Promise => { diff --git a/e2e/specs/divider.spec.ts b/e2e/specs/divider.spec.ts index e843d915..47b29f68 100644 --- a/e2e/specs/divider.spec.ts +++ b/e2e/specs/divider.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; test('empty vertical Dividers respect explicit height and flex stretching', async ({ page }: { page: Page }): Promise => { await page.goto('/divider'); diff --git a/e2e/specs/drawer.spec.ts b/e2e/specs/drawer.spec.ts index c4648575..c95b84b2 100644 --- a/e2e/specs/drawer.spec.ts +++ b/e2e/specs/drawer.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; interface BoxGeometry { readonly height: number; diff --git a/e2e/specs/file-drop.spec.ts b/e2e/specs/file-drop.spec.ts index c90821c9..29ae3860 100644 --- a/e2e/specs/file-drop.spec.ts +++ b/e2e/specs/file-drop.spec.ts @@ -1,4 +1,4 @@ -import { expect, JSHandle, Locator, Page, test } from '@playwright/test'; +import { expect, type JSHandle, type Locator, type Page, test } from '@playwright/test'; test('File Drop accepts supported files and reports rejected files through a real drop event', async ({ page, diff --git a/e2e/specs/focus.spec.ts b/e2e/specs/focus.spec.ts index 0468bac3..c548d77b 100644 --- a/e2e/specs/focus.spec.ts +++ b/e2e/specs/focus.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; interface FocusCase { readonly name: string; diff --git a/e2e/specs/gallery.spec.ts b/e2e/specs/gallery.spec.ts index eff270fb..3eaa8222 100644 --- a/e2e/specs/gallery.spec.ts +++ b/e2e/specs/gallery.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; async function getThumbnailButtons(gallery: Locator): Promise { const buttons: Locator = gallery.getByRole('group', { name: 'Choose image' }).getByRole('button'); diff --git a/e2e/specs/listbox.spec.ts b/e2e/specs/listbox.spec.ts index 32f0484f..1cb88fa4 100644 --- a/e2e/specs/listbox.spec.ts +++ b/e2e/specs/listbox.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; test('Listbox selects the focused option with Space', async ({ page }: { page: Page }): Promise => { await page.goto('/listbox'); diff --git a/e2e/specs/menu.spec.ts b/e2e/specs/menu.spec.ts index ab8fc972..1e0ab95a 100644 --- a/e2e/specs/menu.spec.ts +++ b/e2e/specs/menu.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; interface Bounds { readonly x: number; diff --git a/e2e/specs/mobile-overlay.spec.ts b/e2e/specs/mobile-overlay.spec.ts index 466264c9..507f2d1b 100644 --- a/e2e/specs/mobile-overlay.spec.ts +++ b/e2e/specs/mobile-overlay.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; interface Bounds { readonly x: number; diff --git a/e2e/specs/multi-select.spec.ts b/e2e/specs/multi-select.spec.ts index f3fd50c5..ec8cd00a 100644 --- a/e2e/specs/multi-select.spec.ts +++ b/e2e/specs/multi-select.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; test('Multi-Select remains open while adding multiple options', async ({ page }: { page: Page }): Promise => { await page.goto('/multi-select'); diff --git a/e2e/specs/order-list.spec.ts b/e2e/specs/order-list.spec.ts index 00ecbfe6..afe5dac8 100644 --- a/e2e/specs/order-list.spec.ts +++ b/e2e/specs/order-list.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; interface Bounds { readonly x: number; diff --git a/e2e/specs/popover.spec.ts b/e2e/specs/popover.spec.ts index f8459e92..337af7f0 100644 --- a/e2e/specs/popover.spec.ts +++ b/e2e/specs/popover.spec.ts @@ -1,6 +1,6 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; -import { Bounds, visibleBounds } from '../support/geometry'; +import { type Bounds, visibleBounds } from '../support/geometry'; test('Popover opens outside its trigger and restores focus on Escape', async ({ page }: { page: Page }): Promise => { await page.goto('/popover'); diff --git a/e2e/specs/responsive-layout.spec.ts b/e2e/specs/responsive-layout.spec.ts index 87b4781d..0d8f65a9 100644 --- a/e2e/specs/responsive-layout.spec.ts +++ b/e2e/specs/responsive-layout.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; interface Bounds { readonly x: number; diff --git a/e2e/specs/select-dialog.spec.ts b/e2e/specs/select-dialog.spec.ts index 8119cc97..2e01cc01 100644 --- a/e2e/specs/select-dialog.spec.ts +++ b/e2e/specs/select-dialog.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; async function activate(control: Locator, isMobile: boolean): Promise { if (isMobile) await control.tap(); diff --git a/e2e/specs/select.spec.ts b/e2e/specs/select.spec.ts index a7580e71..11cee23e 100644 --- a/e2e/specs/select.spec.ts +++ b/e2e/specs/select.spec.ts @@ -1,6 +1,6 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; -import { Bounds, visibleBounds } from '../support/geometry'; +import { type Bounds, visibleBounds } from '../support/geometry'; test('Select popup follows its control and commits an option', async ({ page }: { page: Page }): Promise => { await page.goto('/select'); diff --git a/e2e/specs/sidebar.spec.ts b/e2e/specs/sidebar.spec.ts index c9547bda..d1e1cf6b 100644 --- a/e2e/specs/sidebar.spec.ts +++ b/e2e/specs/sidebar.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; test.use({ viewport: { width: 1280, height: 800 } }); diff --git a/e2e/specs/tabs.spec.ts b/e2e/specs/tabs.spec.ts index 84b65323..58a93178 100644 --- a/e2e/specs/tabs.spec.ts +++ b/e2e/specs/tabs.spec.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; test('Tabs follow arrow focus, activate the focused tab, and skip disabled tabs', async ({ page, diff --git a/e2e/specs/toast.spec.ts b/e2e/specs/toast.spec.ts index 6cdf037f..b040eaa3 100644 --- a/e2e/specs/toast.spec.ts +++ b/e2e/specs/toast.spec.ts @@ -1,6 +1,6 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; -import { Bounds, visibleBounds } from '../support/geometry'; +import { type Bounds, visibleBounds } from '../support/geometry'; test('Toast places actions below its message and completes the action flow', async ({ page }: { page: Page }): Promise => { await page.goto('/toast'); diff --git a/e2e/specs/tooltip.spec.ts b/e2e/specs/tooltip.spec.ts index bfd31905..93e2b668 100644 --- a/e2e/specs/tooltip.spec.ts +++ b/e2e/specs/tooltip.spec.ts @@ -1,6 +1,6 @@ -import { expect, Locator, Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; -import { Bounds, visibleBounds } from '../support/geometry'; +import { type Bounds, visibleBounds } from '../support/geometry'; test('Tooltip is exposed as the focused control description and honors top placement', async ({ page, diff --git a/e2e/support/geometry.ts b/e2e/support/geometry.ts index 744fdc90..e84b7f68 100644 --- a/e2e/support/geometry.ts +++ b/e2e/support/geometry.ts @@ -1,4 +1,4 @@ -import { Locator } from '@playwright/test'; +import { type Locator } from '@playwright/test'; export interface Bounds { readonly height: number; diff --git a/eslint.config.mjs b/eslint.config.mjs index 7f97adf5..6dc4ef62 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -79,6 +79,9 @@ export default defineConfig( }, ], + '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports', fixStyle: 'inline-type-imports' }], + '@typescript-eslint/consistent-type-exports': 'error', + '@typescript-eslint/consistent-generic-constructors': ['error', 'type-annotation'], '@typescript-eslint/explicit-function-return-type': 'error', '@typescript-eslint/explicit-module-boundary-types': 'error', '@typescript-eslint/typedef': [ @@ -92,6 +95,7 @@ export default defineConfig( }, ], + 'no-nested-ternary': 'error', complexity: ['error', 12], 'max-lines-per-function': [ 'error', diff --git a/playground/src/app/app.config.ts b/playground/src/app/app.config.ts index c44766bc..65dd3615 100644 --- a/playground/src/app/app.config.ts +++ b/playground/src/app/app.config.ts @@ -1,4 +1,4 @@ -import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; +import { type ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; import { provideRouter, withComponentInputBinding, withInMemoryScrolling } from '@angular/router'; import { routes } from './app.routes'; diff --git a/playground/src/app/app.ts b/playground/src/app/app.ts index 65835ed1..66f442f4 100644 --- a/playground/src/app/app.ts +++ b/playground/src/app/app.ts @@ -5,12 +5,12 @@ import { ChangeDetectionStrategy, Component, computed, - ElementRef, + type ElementRef, inject, - Signal, + type Signal, signal, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { NavigationEnd, Router, RouterLink, RouterOutlet } from '@angular/router'; @@ -24,7 +24,7 @@ import { Navbar, NavbarAction, NavbarBrand, - NavbarItem, + type NavbarItem, Sidebar, SidebarGroupTemplate, SidebarHeader, @@ -100,7 +100,7 @@ export class App { ), { initialValue: this.location.path(true) || this.router.url }, ); - protected readonly currentPath: Signal = computed((): string => this.currentUrl().split(/[?#]/, 1)[0] || '/'); + protected readonly currentPath: Signal = computed((): string => this.currentUrl().split(/[?#]/, 1)[0] ?? '/'); protected readonly theme: WritableSignal = signal(this.getInitialTheme()); public constructor() { diff --git a/playground/src/app/pages/accordion/accordion.page.ts b/playground/src/app/pages/accordion/accordion.page.ts index 3b5e868f..e6711813 100644 --- a/playground/src/app/pages/accordion/accordion.page.ts +++ b/playground/src/app/pages/accordion/accordion.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import { AccordionActionsExample } from './examples/actions/actions.example'; import actionsHtml from './examples/actions/actions.example.html'; import * as actionsTs from './examples/actions/actions.example.ts' with { loader: 'text' }; diff --git a/playground/src/app/pages/accordion/examples/actions/actions.example.ts b/playground/src/app/pages/accordion/examples/actions/actions.example.ts index 4a645303..254a9c34 100644 --- a/playground/src/app/pages/accordion/examples/actions/actions.example.ts +++ b/playground/src/app/pages/accordion/examples/actions/actions.example.ts @@ -1,11 +1,11 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucidePlus } from '@lucide/angular'; import { Accordion, AccordionActionsTemplate, AccordionItem, - AccordionItemData, - AccordionValue, + type AccordionItemData, + type AccordionValue, Button, } from '@sushi-kit/angular'; @@ -23,7 +23,7 @@ export class AccordionActionsExample { protected add(item: AccordionItemData): void { this.entries.update((entries: Readonly>): Readonly> => ({ ...entries, - [item.value]: entries[item.value] + 1, + [item.value]: (entries[item.value] ?? 0) + 1, })); this.message.set(`Added an entry to ${item.label}.`); diff --git a/playground/src/app/pages/accordion/examples/basic/basic.example.ts b/playground/src/app/pages/accordion/examples/basic/basic.example.ts index 2b82985f..5d594c77 100644 --- a/playground/src/app/pages/accordion/examples/basic/basic.example.ts +++ b/playground/src/app/pages/accordion/examples/basic/basic.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Accordion, AccordionItem } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/accordion/examples/control/control.example.ts b/playground/src/app/pages/accordion/examples/control/control.example.ts index 64c26a3c..3131b3e8 100644 --- a/playground/src/app/pages/accordion/examples/control/control.example.ts +++ b/playground/src/app/pages/accordion/examples/control/control.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Accordion, AccordionItem, Button, Input } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/accordion/examples/templates/templates.example.ts b/playground/src/app/pages/accordion/examples/templates/templates.example.ts index 65fec311..8f2b8ebf 100644 --- a/playground/src/app/pages/accordion/examples/templates/templates.example.ts +++ b/playground/src/app/pages/accordion/examples/templates/templates.example.ts @@ -1,10 +1,10 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Accordion, AccordionHeaderTemplate, AccordionIndicatorTemplate, AccordionItem, - AccordionItemData, + type AccordionItemData, Badge, Status, } from '@sushi-kit/angular'; @@ -35,6 +35,8 @@ export class AccordionTemplatesExample { } protected environment(item: AccordionItemData): string { - return item.value === 'production' ? 'Healthy' : item.value === 'preview' ? 'Review' : 'Paused'; + if (item.value === 'production') return 'Healthy'; + if (item.value === 'preview') return 'Review'; + return 'Paused'; } } diff --git a/playground/src/app/pages/api/api.page.ts b/playground/src/app/pages/api/api.page.ts index 29fa8846..6b912399 100644 --- a/playground/src/app/pages/api/api.page.ts +++ b/playground/src/app/pages/api/api.page.ts @@ -1,5 +1,5 @@ import { DOCUMENT } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, inject, input, InputSignal, Signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, inject, input, type InputSignal, type Signal } from '@angular/core'; import { Button, Card } from '@sushi-kit/angular'; import { ApiReference } from '../../shared/api-reference/api-reference.component'; import { apiFragmentHref, navigateToApiTarget } from '../../shared/api-reference/api-reference-navigation'; diff --git a/playground/src/app/pages/auto-focus/auto-focus.page.ts b/playground/src/app/pages/auto-focus/auto-focus.page.ts index f9a8af3d..38ca42b9 100644 --- a/playground/src/app/pages/auto-focus/auto-focus.page.ts +++ b/playground/src/app/pages/auto-focus/auto-focus.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import disabledHtml from './examples/disabled/disabled.example.html'; import * as disabledTs from './examples/disabled/disabled.example.ts' with { loader: 'text' }; import { AutoFocusDisabledExample } from './examples/disabled/disabled.example'; diff --git a/playground/src/app/pages/auto-focus/examples/dynamic/dynamic.example.ts b/playground/src/app/pages/auto-focus/examples/dynamic/dynamic.example.ts index d3b3d26e..648e77bb 100644 --- a/playground/src/app/pages/auto-focus/examples/dynamic/dynamic.example.ts +++ b/playground/src/app/pages/auto-focus/examples/dynamic/dynamic.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { AutoFocus, Button, Input, Join, JoinItem } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/autocomplete/autocomplete.page.ts b/playground/src/app/pages/autocomplete/autocomplete.page.ts index adb25cde..4a755571 100644 --- a/playground/src/app/pages/autocomplete/autocomplete.page.ts +++ b/playground/src/app/pages/autocomplete/autocomplete.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import asyncHtml from './examples/async/async.example.html'; import * as asyncTs from './examples/async/async.example.ts' with { loader: 'text' }; import { AutocompleteAsyncExample } from './examples/async/async.example'; diff --git a/playground/src/app/pages/autocomplete/examples/async/async.example.ts b/playground/src/app/pages/autocomplete/examples/async/async.example.ts index fe96619b..375d9664 100644 --- a/playground/src/app/pages/autocomplete/examples/async/async.example.ts +++ b/playground/src/app/pages/autocomplete/examples/async/async.example.ts @@ -1,10 +1,10 @@ -import { ChangeDetectionStrategy, Component, DestroyRef, inject, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, DestroyRef, inject, signal, type WritableSignal } from '@angular/core'; import { Autocomplete, AutocompleteEmptyTemplate, AutocompleteErrorTemplate, AutocompleteLoadingTemplate, - AutocompleteOption, + type AutocompleteOption, Label, Spinner, } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/autocomplete/examples/basic/basic.example.ts b/playground/src/app/pages/autocomplete/examples/basic/basic.example.ts index 8b8a3ef0..def99ee4 100644 --- a/playground/src/app/pages/autocomplete/examples/basic/basic.example.ts +++ b/playground/src/app/pages/autocomplete/examples/basic/basic.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; -import { Autocomplete, AutocompleteOption, AutocompleteValue, Button, Join, JoinItem, Label } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; +import { Autocomplete, type AutocompleteOption, type AutocompleteValue, Button, Join, JoinItem, Label } from '@sushi-kit/angular'; interface CityForm { city: AutocompleteValue; diff --git a/playground/src/app/pages/autocomplete/examples/filtering/filtering.example.ts b/playground/src/app/pages/autocomplete/examples/filtering/filtering.example.ts index 36b3fdb7..dd9aebf3 100644 --- a/playground/src/app/pages/autocomplete/examples/filtering/filtering.example.ts +++ b/playground/src/app/pages/autocomplete/examples/filtering/filtering.example.ts @@ -1,15 +1,15 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideMapPin } from '@lucide/angular'; import { Autocomplete, Button, - AutocompleteCompareWith, + type AutocompleteCompareWith, AutocompleteEmptyTemplate, - AutocompleteFilter, + type AutocompleteFilter, AutocompleteItemTemplate, - AutocompleteOption, + type AutocompleteOption, AutocompletePrefixTemplate, - AutocompleteValue, + type AutocompleteValue, Label, } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/autocomplete/examples/states/states.example.ts b/playground/src/app/pages/autocomplete/examples/states/states.example.ts index fc458568..8aa1ce79 100644 --- a/playground/src/app/pages/autocomplete/examples/states/states.example.ts +++ b/playground/src/app/pages/autocomplete/examples/states/states.example.ts @@ -1,5 +1,13 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Autocomplete, AutocompleteOption, Badge, ComponentSize, Fieldset, FieldsetLegend, Label } from '@sushi-kit/angular'; +import { + Autocomplete, + type AutocompleteOption, + Badge, + type ComponentSize, + Fieldset, + FieldsetLegend, + Label, +} from '@sushi-kit/angular'; @Component({ selector: 'pg-autocomplete-states-example', diff --git a/playground/src/app/pages/avatar/avatar.page.ts b/playground/src/app/pages/avatar/avatar.page.ts index 10a098d9..244047d1 100644 --- a/playground/src/app/pages/avatar/avatar.page.ts +++ b/playground/src/app/pages/avatar/avatar.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import profilesHtml from './examples/profiles/profiles.example.html'; import * as profilesTs from './examples/profiles/profiles.example.ts' with { loader: 'text' }; import { AvatarProfilesExample } from './examples/profiles/profiles.example'; diff --git a/playground/src/app/pages/avatar/examples/usage/usage.example.ts b/playground/src/app/pages/avatar/examples/usage/usage.example.ts index f021a793..c104f3ba 100644 --- a/playground/src/app/pages/avatar/examples/usage/usage.example.ts +++ b/playground/src/app/pages/avatar/examples/usage/usage.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Avatar, AvatarShape, AvatarSize } from '@sushi-kit/angular'; +import { Avatar, type AvatarShape, type AvatarSize } from '@sushi-kit/angular'; @Component({ selector: 'pg-avatar-usage-example', diff --git a/playground/src/app/pages/badge/badge.page.ts b/playground/src/app/pages/badge/badge.page.ts index 7c346ce1..f1ed5c21 100644 --- a/playground/src/app/pages/badge/badge.page.ts +++ b/playground/src/app/pages/badge/badge.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import contextHtml from './examples/context/context.example.html'; import * as contextTs from './examples/context/context.example.ts' with { loader: 'text' }; import { BadgeContextExample } from './examples/context/context.example'; diff --git a/playground/src/app/pages/badge/examples/context/context.example.ts b/playground/src/app/pages/badge/examples/context/context.example.ts index 5c88cc12..dc383726 100644 --- a/playground/src/app/pages/badge/examples/context/context.example.ts +++ b/playground/src/app/pages/badge/examples/context/context.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Button, Card, List, ListItemTemplate, ThemeSeverity } from '@sushi-kit/angular'; +import { Badge, Button, Card, List, ListItemTemplate, type ThemeSeverity } from '@sushi-kit/angular'; interface Environment { readonly name: string; diff --git a/playground/src/app/pages/badge/examples/usage/usage.example.ts b/playground/src/app/pages/badge/examples/usage/usage.example.ts index ea17c866..3f2b6fb7 100644 --- a/playground/src/app/pages/badge/examples/usage/usage.example.ts +++ b/playground/src/app/pages/badge/examples/usage/usage.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, BadgeSeverity, BadgeSize, BadgeVariant } from '@sushi-kit/angular'; +import { Badge, type BadgeSeverity, type BadgeSize, type BadgeVariant } from '@sushi-kit/angular'; @Component({ selector: 'pg-badge-usage-example', diff --git a/playground/src/app/pages/breadcrumb/breadcrumb.page.ts b/playground/src/app/pages/breadcrumb/breadcrumb.page.ts index d5d2464d..20b3ad20 100644 --- a/playground/src/app/pages/breadcrumb/breadcrumb.page.ts +++ b/playground/src/app/pages/breadcrumb/breadcrumb.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import html from './examples/usage/usage.example.html'; import * as ts from './examples/usage/usage.example.ts' with { loader: 'text' }; import { BreadcrumbUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/breadcrumb/examples/usage/usage.example.ts b/playground/src/app/pages/breadcrumb/examples/usage/usage.example.ts index 6188fbaf..5ca48066 100644 --- a/playground/src/app/pages/breadcrumb/examples/usage/usage.example.ts +++ b/playground/src/app/pages/breadcrumb/examples/usage/usage.example.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { LucideHouse } from '@lucide/angular'; -import { Breadcrumb, BreadcrumbItem, BreadcrumbItemTemplate } from '@sushi-kit/angular'; +import { Breadcrumb, type BreadcrumbItem, BreadcrumbItemTemplate } from '@sushi-kit/angular'; @Component({ selector: 'pg-breadcrumb-usage-example', diff --git a/playground/src/app/pages/button/button.page.ts b/playground/src/app/pages/button/button.page.ts index 77df7a5f..0ab407f2 100644 --- a/playground/src/app/pages/button/button.page.ts +++ b/playground/src/app/pages/button/button.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import appearanceHtml from './examples/appearance/appearance.example.html'; import * as appearanceTs from './examples/appearance/appearance.example.ts' with { loader: 'text' }; import { ButtonAppearanceExample } from './examples/appearance/appearance.example'; diff --git a/playground/src/app/pages/button/examples/appearance/appearance.example.ts b/playground/src/app/pages/button/examples/appearance/appearance.example.ts index c1b58c4a..58372685 100644 --- a/playground/src/app/pages/button/examples/appearance/appearance.example.ts +++ b/playground/src/app/pages/button/examples/appearance/appearance.example.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { LucideEllipsis, LucidePlus } from '@lucide/angular'; -import { Button, ButtonSeverity, ButtonSize, ButtonVariant } from '@sushi-kit/angular'; +import { Button, type ButtonSeverity, type ButtonSize, type ButtonVariant } from '@sushi-kit/angular'; @Component({ selector: 'pg-button-appearance-example', diff --git a/playground/src/app/pages/button/examples/states/states.example.ts b/playground/src/app/pages/button/examples/states/states.example.ts index 70aaa504..8b3e4a06 100644 --- a/playground/src/app/pages/button/examples/states/states.example.ts +++ b/playground/src/app/pages/button/examples/states/states.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Button, Spinner } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/card/card.page.ts b/playground/src/app/pages/card/card.page.ts index 7598378c..c4f18ec0 100644 --- a/playground/src/app/pages/card/card.page.ts +++ b/playground/src/app/pages/card/card.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import appearanceHtml from './examples/appearance/appearance.example.html'; import * as appearanceTs from './examples/appearance/appearance.example.ts' with { loader: 'text' }; import { CardAppearanceExample } from './examples/appearance/appearance.example'; diff --git a/playground/src/app/pages/card/examples/appearance/appearance.example.ts b/playground/src/app/pages/card/examples/appearance/appearance.example.ts index cecaffde..84b30187 100644 --- a/playground/src/app/pages/card/examples/appearance/appearance.example.ts +++ b/playground/src/app/pages/card/examples/appearance/appearance.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Card, CardSize, CardTitle, CardVariant } from '@sushi-kit/angular'; +import { Card, type CardSize, CardTitle, type CardVariant } from '@sushi-kit/angular'; interface CardPreview { readonly size: CardSize; diff --git a/playground/src/app/pages/checkbox/checkbox.page.ts b/playground/src/app/pages/checkbox/checkbox.page.ts index 920ed241..5de82f39 100644 --- a/playground/src/app/pages/checkbox/checkbox.page.ts +++ b/playground/src/app/pages/checkbox/checkbox.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import usageHtml from './examples/usage/usage.example.html'; import * as usageTs from './examples/usage/usage.example.ts' with { loader: 'text' }; import { CheckboxUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/checkbox/examples/usage/usage.example.ts b/playground/src/app/pages/checkbox/examples/usage/usage.example.ts index 824f0600..ee44df21 100644 --- a/playground/src/app/pages/checkbox/examples/usage/usage.example.ts +++ b/playground/src/app/pages/checkbox/examples/usage/usage.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { Button, Card, Checkbox, Label } from '@sushi-kit/angular'; interface Preferences { diff --git a/playground/src/app/pages/chip/chip.page.ts b/playground/src/app/pages/chip/chip.page.ts index 0fc2073c..884e58cd 100644 --- a/playground/src/app/pages/chip/chip.page.ts +++ b/playground/src/app/pages/chip/chip.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { ChipBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/chip/examples/removal/removal.example.ts b/playground/src/app/pages/chip/examples/removal/removal.example.ts index e9a92946..f6da4218 100644 --- a/playground/src/app/pages/chip/examples/removal/removal.example.ts +++ b/playground/src/app/pages/chip/examples/removal/removal.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Button, Chip } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/code/code.page.ts b/playground/src/app/pages/code/code.page.ts index 6b8208dc..1e0c1a51 100644 --- a/playground/src/app/pages/code/code.page.ts +++ b/playground/src/app/pages/code/code.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { CodeBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/color-picker/color-picker.page.ts b/playground/src/app/pages/color-picker/color-picker.page.ts index ac5dc1af..5808de97 100644 --- a/playground/src/app/pages/color-picker/color-picker.page.ts +++ b/playground/src/app/pages/color-picker/color-picker.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { ColorPickerBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/color-picker/examples/basic/basic.example.ts b/playground/src/app/pages/color-picker/examples/basic/basic.example.ts index 6f4ab91a..de6d7c6f 100644 --- a/playground/src/app/pages/color-picker/examples/basic/basic.example.ts +++ b/playground/src/app/pages/color-picker/examples/basic/basic.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { Button, ColorPicker, JoinItem, Label } from '@sushi-kit/angular'; interface ColorModel { diff --git a/playground/src/app/pages/color-picker/examples/presets/presets.example.ts b/playground/src/app/pages/color-picker/examples/presets/presets.example.ts index 5c37d681..bb009fbf 100644 --- a/playground/src/app/pages/color-picker/examples/presets/presets.example.ts +++ b/playground/src/app/pages/color-picker/examples/presets/presets.example.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { LucideCheck } from '@lucide/angular'; -import { ColorPicker, ColorPickerPreset, ColorPickerPresetTemplate, Label } from '@sushi-kit/angular'; +import { ColorPicker, type ColorPickerPreset, ColorPickerPresetTemplate, Label } from '@sushi-kit/angular'; @Component({ selector: 'pg-color-picker-presets-example', diff --git a/playground/src/app/pages/data-view/data-view.page.ts b/playground/src/app/pages/data-view/data-view.page.ts index baff0280..adbad92d 100644 --- a/playground/src/app/pages/data-view/data-view.page.ts +++ b/playground/src/app/pages/data-view/data-view.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { DataViewBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/data-view/examples/infinite/infinite.example.ts b/playground/src/app/pages/data-view/examples/infinite/infinite.example.ts index 5f1f92b0..520d8fa1 100644 --- a/playground/src/app/pages/data-view/examples/infinite/infinite.example.ts +++ b/playground/src/app/pages/data-view/examples/infinite/infinite.example.ts @@ -1,4 +1,13 @@ -import { ChangeDetectionStrategy, Component, computed, Signal, signal, WritableSignal } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + inject, + computed, + type Signal, + signal, + type WritableSignal, +} from '@angular/core'; import { Badge, Button, @@ -18,10 +27,16 @@ interface Resource { readonly summary: string; } +function resourceArea(index: number): string { + if (index % 3 === 0) return 'Accessibility'; + if (index % 2 === 0) return 'Components'; + return 'Foundations'; +} + const resources: readonly Resource[] = Array.from({ length: 18 }, (_value: unknown, index: number): Resource => ({ id: index + 1, title: `Pattern collection ${index + 1}`, - area: index % 3 === 0 ? 'Accessibility' : index % 2 === 0 ? 'Components' : 'Foundations', + area: resourceArea(index), summary: 'A practical reference for consistent product experiences.', })); @@ -37,10 +52,17 @@ export class DataViewInfiniteExample { protected readonly items: Signal = computed((): readonly Resource[] => resources.slice(0, this.count())); protected readonly hasMore: Signal = computed((): boolean => this.count() < resources.length); + private readonly destroyRef: DestroyRef = inject(DestroyRef); + private timer: ReturnType | undefined; + + public constructor() { + this.destroyRef.onDestroy((): void => clearTimeout(this.timer)); + } + protected loadMore(): void { if (this.loading()) return; this.loading.set(true); - window.setTimeout((): void => { + this.timer = setTimeout((): void => { this.count.update((count: number): number => Math.min(resources.length, count + 3)); this.loading.set(false); }, 1000); diff --git a/playground/src/app/pages/data-view/examples/layout/layout.example.ts b/playground/src/app/pages/data-view/examples/layout/layout.example.ts index 4467b240..88e34550 100644 --- a/playground/src/app/pages/data-view/examples/layout/layout.example.ts +++ b/playground/src/app/pages/data-view/examples/layout/layout.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Badge, Button, @@ -8,7 +8,7 @@ import { DataView, DataViewHeaderTemplate, DataViewItemTemplate, - DataViewLayout, + type DataViewLayout, } from '@sushi-kit/angular'; import { LucideShoppingCart } from '@lucide/angular'; diff --git a/playground/src/app/pages/data-view/examples/remote/remote.example.ts b/playground/src/app/pages/data-view/examples/remote/remote.example.ts index 8fe9203e..d6578e75 100644 --- a/playground/src/app/pages/data-view/examples/remote/remote.example.ts +++ b/playground/src/app/pages/data-view/examples/remote/remote.example.ts @@ -1,4 +1,13 @@ -import { ChangeDetectionStrategy, Component, computed, Signal, signal, WritableSignal } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + inject, + computed, + type Signal, + signal, + type WritableSignal, +} from '@angular/core'; import { Badge, Button, @@ -47,9 +56,17 @@ export class DataViewRemoteExample { return deployments.slice(start, start + 3); }); + private readonly destroyRef: DestroyRef = inject(DestroyRef); + private timer: ReturnType | undefined; + + public constructor() { + this.destroyRef.onDestroy((): void => clearTimeout(this.timer)); + } + protected handlePage(page: number): void { + clearTimeout(this.timer); this.loading.set(true); this.page.set(page); - window.setTimeout((): void => this.loading.set(false), 450); + this.timer = setTimeout((): void => this.loading.set(false), 450); } } diff --git a/playground/src/app/pages/dialog/dialog.page.ts b/playground/src/app/pages/dialog/dialog.page.ts index b52d5763..3d7c9b9b 100644 --- a/playground/src/app/pages/dialog/dialog.page.ts +++ b/playground/src/app/pages/dialog/dialog.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import { DialogAdvancedExample } from './examples/advanced/advanced.example'; import * as advancedCss from './examples/advanced/advanced.example.css' with { loader: 'text' }; import advancedHtml from './examples/advanced/advanced.example.html'; diff --git a/playground/src/app/pages/dialog/examples/content-resize/content-resize.example.ts b/playground/src/app/pages/dialog/examples/content-resize/content-resize.example.ts index 1aa29d4b..8a175b86 100644 --- a/playground/src/app/pages/dialog/examples/content-resize/content-resize.example.ts +++ b/playground/src/app/pages/dialog/examples/content-resize/content-resize.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Button, Dialog, diff --git a/playground/src/app/pages/dialog/examples/coordinates/coordinates.example.ts b/playground/src/app/pages/dialog/examples/coordinates/coordinates.example.ts index effcfcc5..78a32f43 100644 --- a/playground/src/app/pages/dialog/examples/coordinates/coordinates.example.ts +++ b/playground/src/app/pages/dialog/examples/coordinates/coordinates.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Button, Dialog, DialogBody, DialogHeader, DialogPosition, DialogTrigger } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Button, Dialog, DialogBody, DialogHeader, type DialogPosition, DialogTrigger } from '@sushi-kit/angular'; @Component({ selector: 'pg-dialog-coordinates-example', diff --git a/playground/src/app/pages/dialog/examples/dynamic/dynamic.example.ts b/playground/src/app/pages/dialog/examples/dynamic/dynamic.example.ts index 44c5bdc8..664ae08a 100644 --- a/playground/src/app/pages/dialog/examples/dynamic/dynamic.example.ts +++ b/playground/src/app/pages/dialog/examples/dynamic/dynamic.example.ts @@ -1,5 +1,5 @@ import { Dialog as CdkDialog, DIALOG_DATA, DialogRef } from '@angular/cdk/dialog'; -import { ChangeDetectionStrategy, Component, inject, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, signal, type WritableSignal } from '@angular/core'; import { Button } from '@sushi-kit/angular'; interface MenuDialogData { diff --git a/playground/src/app/pages/dialog/examples/form/form.example.ts b/playground/src/app/pages/dialog/examples/form/form.example.ts index b71812a7..66b6d2c6 100644 --- a/playground/src/app/pages/dialog/examples/form/form.example.ts +++ b/playground/src/app/pages/dialog/examples/form/form.example.ts @@ -1,10 +1,10 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Button, Checkbox, Dialog, DialogClose, - DialogCloseEvent, + type DialogCloseEvent, DialogBody, DialogFooter, DialogHeader, @@ -14,8 +14,8 @@ import { Input, Label, Select, - SelectModelValue, - SelectOption, + type SelectModelValue, + type SelectOption, Textarea, } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/divider/divider.page.ts b/playground/src/app/pages/divider/divider.page.ts index da7a4b4f..fa97ac06 100644 --- a/playground/src/app/pages/divider/divider.page.ts +++ b/playground/src/app/pages/divider/divider.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import layoutHtml from './examples/layout/layout.example.html'; import * as layoutTs from './examples/layout/layout.example.ts' with { loader: 'text' }; import { DividerLayoutExample } from './examples/layout/layout.example'; diff --git a/playground/src/app/pages/drawer/drawer.page.ts b/playground/src/app/pages/drawer/drawer.page.ts index 33f66111..8314ec4d 100644 --- a/playground/src/app/pages/drawer/drawer.page.ts +++ b/playground/src/app/pages/drawer/drawer.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge, Code, CodeLine } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import { highlightLines } from '../../shared/example-code/example-highlighter'; import { DrawerCartExample } from './examples/cart/cart.example'; import cartHtml from './examples/cart/cart.example.html'; diff --git a/playground/src/app/pages/drawer/examples/cart/cart.example.ts b/playground/src/app/pages/drawer/examples/cart/cart.example.ts index 03cb1d04..c674fc8b 100644 --- a/playground/src/app/pages/drawer/examples/cart/cart.example.ts +++ b/playground/src/app/pages/drawer/examples/cart/cart.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, computed, Signal, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, type Signal, signal, type WritableSignal } from '@angular/core'; import { LucideMinus, LucidePlus, LucideShoppingBag, LucideTrash, LucideX } from '@lucide/angular'; import { Badge, diff --git a/playground/src/app/pages/fieldset/examples/controlled/controlled.example.ts b/playground/src/app/pages/fieldset/examples/controlled/controlled.example.ts index 4c9d8fb5..5684bf12 100644 --- a/playground/src/app/pages/fieldset/examples/controlled/controlled.example.ts +++ b/playground/src/app/pages/fieldset/examples/controlled/controlled.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Button, Divider, Fieldset, FieldsetContent, FieldsetLegend, FieldsetToggle, Input, Label } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/fieldset/examples/toggles/toggles.example.ts b/playground/src/app/pages/fieldset/examples/toggles/toggles.example.ts index dd89801b..9b07200e 100644 --- a/playground/src/app/pages/fieldset/examples/toggles/toggles.example.ts +++ b/playground/src/app/pages/fieldset/examples/toggles/toggles.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideChevronRight } from '@lucide/angular'; import { Button, Fieldset, FieldsetContent, FieldsetLegend, FieldsetToggle, Input, Label } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/fieldset/fieldset.page.ts b/playground/src/app/pages/fieldset/fieldset.page.ts index e7094d28..574de8e9 100644 --- a/playground/src/app/pages/fieldset/fieldset.page.ts +++ b/playground/src/app/pages/fieldset/fieldset.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import controlledHtml from './examples/controlled/controlled.example.html'; import * as controlledTs from './examples/controlled/controlled.example.ts' with { loader: 'text' }; import { FieldsetControlledExample } from './examples/controlled/controlled.example'; diff --git a/playground/src/app/pages/file-drop/examples/basic/basic.example.ts b/playground/src/app/pages/file-drop/examples/basic/basic.example.ts index 4f2e9299..5a088d1b 100644 --- a/playground/src/app/pages/file-drop/examples/basic/basic.example.ts +++ b/playground/src/app/pages/file-drop/examples/basic/basic.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; -import { FileDrop, FileDropRejection } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; +import { FileDrop, type FileDropRejection } from '@sushi-kit/angular'; interface FilesModel { files: readonly File[]; diff --git a/playground/src/app/pages/file-drop/examples/custom/custom.example.ts b/playground/src/app/pages/file-drop/examples/custom/custom.example.ts index dd2e97b0..e60f6605 100644 --- a/playground/src/app/pages/file-drop/examples/custom/custom.example.ts +++ b/playground/src/app/pages/file-drop/examples/custom/custom.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { LucideFileText, LucideUpload } from '@lucide/angular'; import { FileDrop, FileDropContentTemplate, FileDropItemTemplate } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/file-drop/file-drop.page.ts b/playground/src/app/pages/file-drop/file-drop.page.ts index 2f9e4b61..04d7afa2 100644 --- a/playground/src/app/pages/file-drop/file-drop.page.ts +++ b/playground/src/app/pages/file-drop/file-drop.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { FileDropBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/file-input/examples/native/native.example.ts b/playground/src/app/pages/file-input/examples/native/native.example.ts index b293160d..27c0c50a 100644 --- a/playground/src/app/pages/file-input/examples/native/native.example.ts +++ b/playground/src/app/pages/file-input/examples/native/native.example.ts @@ -1,4 +1,12 @@ -import { ChangeDetectionStrategy, Component, ElementRef, signal, Signal, viewChild, WritableSignal } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + type ElementRef, + signal, + type Signal, + viewChild, + type WritableSignal, +} from '@angular/core'; import { Button, Fieldset, FieldsetLegend, FileInput, Label } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/file-input/examples/options/options.example.ts b/playground/src/app/pages/file-input/examples/options/options.example.ts index 402b1361..a2f6b473 100644 --- a/playground/src/app/pages/file-input/examples/options/options.example.ts +++ b/playground/src/app/pages/file-input/examples/options/options.example.ts @@ -1,5 +1,13 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Fieldset, FieldsetLegend, FileInput, FileInputSeverity, FileInputSize, Label } from '@sushi-kit/angular'; +import { + Badge, + Fieldset, + FieldsetLegend, + FileInput, + type FileInputSeverity, + type FileInputSize, + Label, +} from '@sushi-kit/angular'; @Component({ selector: 'pg-file-input-options-example', diff --git a/playground/src/app/pages/file-input/file-input.page.ts b/playground/src/app/pages/file-input/file-input.page.ts index 42da8405..6ef70461 100644 --- a/playground/src/app/pages/file-input/file-input.page.ts +++ b/playground/src/app/pages/file-input/file-input.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { FileInputBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/gallery/examples/usage/usage.example.ts b/playground/src/app/pages/gallery/examples/usage/usage.example.ts index b9b66fe3..2c1a1454 100644 --- a/playground/src/app/pages/gallery/examples/usage/usage.example.ts +++ b/playground/src/app/pages/gallery/examples/usage/usage.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, computed, Signal, signal, WritableSignal } from '@angular/core'; -import { Gallery, GalleryImage, Lightbox } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, computed, type Signal, signal, type WritableSignal } from '@angular/core'; +import { Gallery, type GalleryImage, Lightbox } from '@sushi-kit/angular'; @Component({ selector: 'pg-gallery-usage-example', @@ -76,5 +76,9 @@ export class GalleryUsageExample { ]; protected readonly activeIndex: WritableSignal = signal(0); - protected readonly activeImage: Signal = computed((): GalleryImage => this.images[this.activeIndex()]); + protected readonly activeImage: Signal = computed((): GalleryImage => { + const image: GalleryImage | undefined = this.images[this.activeIndex()]; + if (!image) throw new Error('Expected an active Gallery image.'); + return image; + }); } diff --git a/playground/src/app/pages/gallery/gallery.page.ts b/playground/src/app/pages/gallery/gallery.page.ts index f6928644..4d57c0a1 100644 --- a/playground/src/app/pages/gallery/gallery.page.ts +++ b/playground/src/app/pages/gallery/gallery.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import html from './examples/usage/usage.example.html'; import * as ts from './examples/usage/usage.example.ts' with { loader: 'text' }; import { GalleryUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/icon/icon.page.ts b/playground/src/app/pages/icon/icon.page.ts index c32cee3a..5038cad4 100644 --- a/playground/src/app/pages/icon/icon.page.ts +++ b/playground/src/app/pages/icon/icon.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge, Code, CodeLine } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import usageHtml from './examples/usage/usage.example.html'; import * as usageTs from './examples/usage/usage.example.ts' with { loader: 'text' }; import { IconUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/indicator/examples/positions/positions.example.ts b/playground/src/app/pages/indicator/examples/positions/positions.example.ts index 41961561..097c59df 100644 --- a/playground/src/app/pages/indicator/examples/positions/positions.example.ts +++ b/playground/src/app/pages/indicator/examples/positions/positions.example.ts @@ -1,5 +1,11 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Indicator, IndicatorHorizontalPosition, IndicatorItem, IndicatorVerticalPosition } from '@sushi-kit/angular'; +import { + Badge, + Indicator, + type IndicatorHorizontalPosition, + IndicatorItem, + type IndicatorVerticalPosition, +} from '@sushi-kit/angular'; interface Position { readonly horizontal: IndicatorHorizontalPosition; diff --git a/playground/src/app/pages/indicator/indicator.page.ts b/playground/src/app/pages/indicator/indicator.page.ts index e9d5f5de..1b9e915a 100644 --- a/playground/src/app/pages/indicator/indicator.page.ts +++ b/playground/src/app/pages/indicator/indicator.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { IndicatorBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/input-group/examples/commerce/commerce.example.ts b/playground/src/app/pages/input-group/examples/commerce/commerce.example.ts index 1f1a03fa..d6ee142e 100644 --- a/playground/src/app/pages/input-group/examples/commerce/commerce.example.ts +++ b/playground/src/app/pages/input-group/examples/commerce/commerce.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Button, Input, InputGroup, InputGroupAddon, JoinItem, Label, Select, SelectOption } from '@sushi-kit/angular'; +import { Button, Input, InputGroup, InputGroupAddon, JoinItem, Label, Select, type SelectOption } from '@sushi-kit/angular'; @Component({ selector: 'pg-input-group-commerce-example', diff --git a/playground/src/app/pages/input-group/input-group.page.ts b/playground/src/app/pages/input-group/input-group.page.ts index efe4249a..cfd80544 100644 --- a/playground/src/app/pages/input-group/input-group.page.ts +++ b/playground/src/app/pages/input-group/input-group.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import addressHtml from './examples/address/address.example.html'; import * as addressTs from './examples/address/address.example.ts' with { loader: 'text' }; import { InputGroupAddressExample } from './examples/address/address.example'; diff --git a/playground/src/app/pages/input-number/examples/basic/basic.example.ts b/playground/src/app/pages/input-number/examples/basic/basic.example.ts index f1ef5be9..20809db6 100644 --- a/playground/src/app/pages/input-number/examples/basic/basic.example.ts +++ b/playground/src/app/pages/input-number/examples/basic/basic.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { Button, InputNumber, Label } from '@sushi-kit/angular'; interface BudgetModel { diff --git a/playground/src/app/pages/input-number/input-number.page.ts b/playground/src/app/pages/input-number/input-number.page.ts index 102418dd..b376c44d 100644 --- a/playground/src/app/pages/input-number/input-number.page.ts +++ b/playground/src/app/pages/input-number/input-number.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { InputNumberBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/input-otp/examples/basic/basic.example.ts b/playground/src/app/pages/input-otp/examples/basic/basic.example.ts index f112a8e3..898532a1 100644 --- a/playground/src/app/pages/input-otp/examples/basic/basic.example.ts +++ b/playground/src/app/pages/input-otp/examples/basic/basic.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { Button, InputOtp, Label } from '@sushi-kit/angular'; interface OtpModel { diff --git a/playground/src/app/pages/input-otp/input-otp.page.ts b/playground/src/app/pages/input-otp/input-otp.page.ts index b1d303db..e434fbb1 100644 --- a/playground/src/app/pages/input-otp/input-otp.page.ts +++ b/playground/src/app/pages/input-otp/input-otp.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { InputOtpBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/input-surface/examples/search/search.example.ts b/playground/src/app/pages/input-surface/examples/search/search.example.ts index 84265c39..1f91cf36 100644 --- a/playground/src/app/pages/input-surface/examples/search/search.example.ts +++ b/playground/src/app/pages/input-surface/examples/search/search.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { LucideSearch } from '@lucide/angular'; import { Button, InputSurface, InputSurfaceControl, Join, JoinItem, Kbd, Label } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/input-surface/input-surface.page.ts b/playground/src/app/pages/input-surface/input-surface.page.ts index e2d48ac1..1c1c4f33 100644 --- a/playground/src/app/pages/input-surface/input-surface.page.ts +++ b/playground/src/app/pages/input-surface/input-surface.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import contentHtml from './examples/content/content.example.html'; import * as contentTs from './examples/content/content.example.ts' with { loader: 'text' }; import { InputSurfaceContentExample } from './examples/content/content.example'; diff --git a/playground/src/app/pages/input/examples/appearance/appearance.example.ts b/playground/src/app/pages/input/examples/appearance/appearance.example.ts index 440e62c0..6f8d5071 100644 --- a/playground/src/app/pages/input/examples/appearance/appearance.example.ts +++ b/playground/src/app/pages/input/examples/appearance/appearance.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Fieldset, FieldsetLegend, Input, InputSeverity, InputSize, Label } from '@sushi-kit/angular'; +import { Badge, Fieldset, FieldsetLegend, Input, type InputSeverity, type InputSize, Label } from '@sushi-kit/angular'; @Component({ selector: 'pg-input-appearance-example', diff --git a/playground/src/app/pages/input/examples/basic/basic.example.ts b/playground/src/app/pages/input/examples/basic/basic.example.ts index 7decbdfb..ff31e954 100644 --- a/playground/src/app/pages/input/examples/basic/basic.example.ts +++ b/playground/src/app/pages/input/examples/basic/basic.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField, required, SchemaPathTree } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField, required, type SchemaPathTree } from '@angular/forms/signals'; import { Input, Label } from '@sushi-kit/angular'; interface InputModel { diff --git a/playground/src/app/pages/input/input.page.ts b/playground/src/app/pages/input/input.page.ts index 41aec040..b25231aa 100644 --- a/playground/src/app/pages/input/input.page.ts +++ b/playground/src/app/pages/input/input.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import appearanceHtml from './examples/appearance/appearance.example.html'; import * as appearanceTs from './examples/appearance/appearance.example.ts' with { loader: 'text' }; import { InputAppearanceExample } from './examples/appearance/appearance.example'; diff --git a/playground/src/app/pages/join/examples/nested/nested.example.ts b/playground/src/app/pages/join/examples/nested/nested.example.ts index 13939106..6b072593 100644 --- a/playground/src/app/pages/join/examples/nested/nested.example.ts +++ b/playground/src/app/pages/join/examples/nested/nested.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideChevronDown, LucideChevronUp } from '@lucide/angular'; import { Button, Input, Join, JoinItem } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/join/join.page.ts b/playground/src/app/pages/join/join.page.ts index 04850a9d..c1840027 100644 --- a/playground/src/app/pages/join/join.page.ts +++ b/playground/src/app/pages/join/join.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import actionsHtml from './examples/actions/actions.example.html'; import * as actionsTs from './examples/actions/actions.example.ts' with { loader: 'text' }; import { JoinActionsExample } from './examples/actions/actions.example'; diff --git a/playground/src/app/pages/kbd/examples/usage/usage.example.ts b/playground/src/app/pages/kbd/examples/usage/usage.example.ts index 200836ed..a6779f88 100644 --- a/playground/src/app/pages/kbd/examples/usage/usage.example.ts +++ b/playground/src/app/pages/kbd/examples/usage/usage.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Kbd, KbdSize } from '@sushi-kit/angular'; +import { Kbd, type KbdSize } from '@sushi-kit/angular'; @Component({ selector: 'pg-kbd-usage-example', diff --git a/playground/src/app/pages/kbd/kbd.page.ts b/playground/src/app/pages/kbd/kbd.page.ts index ed8208f1..547c5852 100644 --- a/playground/src/app/pages/kbd/kbd.page.ts +++ b/playground/src/app/pages/kbd/kbd.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import hotkeysHtml from './examples/hotkeys/hotkeys.example.html'; import * as hotkeysTs from './examples/hotkeys/hotkeys.example.ts' with { loader: 'text' }; import { KbdHotkeysExample } from './examples/hotkeys/hotkeys.example'; diff --git a/playground/src/app/pages/label/examples/basic/basic.example.ts b/playground/src/app/pages/label/examples/basic/basic.example.ts index 5704d797..eb8b1a93 100644 --- a/playground/src/app/pages/label/examples/basic/basic.example.ts +++ b/playground/src/app/pages/label/examples/basic/basic.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Input, Label, Select, SelectOption, Textarea } from '@sushi-kit/angular'; +import { Input, Label, Select, type SelectOption, Textarea } from '@sushi-kit/angular'; @Component({ selector: 'pg-label-basic-example', diff --git a/playground/src/app/pages/label/label.page.ts b/playground/src/app/pages/label/label.page.ts index c6d27561..48a91cba 100644 --- a/playground/src/app/pages/label/label.page.ts +++ b/playground/src/app/pages/label/label.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { LabelBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/lightbox/examples/source/source.example.ts b/playground/src/app/pages/lightbox/examples/source/source.example.ts index 73ec68a7..dbd31003 100644 --- a/playground/src/app/pages/lightbox/examples/source/source.example.ts +++ b/playground/src/app/pages/lightbox/examples/source/source.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Button, GalleryImage, Lightbox } from '@sushi-kit/angular'; +import { Button, type GalleryImage, Lightbox } from '@sushi-kit/angular'; @Component({ selector: 'pg-lightbox-source-example', diff --git a/playground/src/app/pages/lightbox/lightbox.page.ts b/playground/src/app/pages/lightbox/lightbox.page.ts index c8090a2e..fb8b15b0 100644 --- a/playground/src/app/pages/lightbox/lightbox.page.ts +++ b/playground/src/app/pages/lightbox/lightbox.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import html from './examples/usage/usage.example.html'; import * as ts from './examples/usage/usage.example.ts' with { loader: 'text' }; import { LightboxUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/list/examples/activity/activity.example.ts b/playground/src/app/pages/list/examples/activity/activity.example.ts index 38ce3ee2..c9577fa4 100644 --- a/playground/src/app/pages/list/examples/activity/activity.example.ts +++ b/playground/src/app/pages/list/examples/activity/activity.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Card, CardTitle, List, ListItemTemplate, Status, ThemeSeverity } from '@sushi-kit/angular'; +import { Badge, Card, CardTitle, List, ListItemTemplate, Status, type ThemeSeverity } from '@sushi-kit/angular'; interface Activity { readonly title: string; diff --git a/playground/src/app/pages/list/examples/tasks/tasks.example.ts b/playground/src/app/pages/list/examples/tasks/tasks.example.ts index a03ac6d5..2a48463e 100644 --- a/playground/src/app/pages/list/examples/tasks/tasks.example.ts +++ b/playground/src/app/pages/list/examples/tasks/tasks.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Card, Checkbox, List, ListItemTemplate, ThemeSeverity } from '@sushi-kit/angular'; +import { Badge, Card, Checkbox, List, ListItemTemplate, type ThemeSeverity } from '@sushi-kit/angular'; interface Task { readonly title: string; diff --git a/playground/src/app/pages/list/list.page.ts b/playground/src/app/pages/list/list.page.ts index 6da0c5e4..d339f2b0 100644 --- a/playground/src/app/pages/list/list.page.ts +++ b/playground/src/app/pages/list/list.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import activityHtml from './examples/activity/activity.example.html'; import * as activityTs from './examples/activity/activity.example.ts' with { loader: 'text' }; import { ListActivityExample } from './examples/activity/activity.example'; diff --git a/playground/src/app/pages/listbox/examples/basic/basic.example.ts b/playground/src/app/pages/listbox/examples/basic/basic.example.ts index 9d4e3efe..50a7495a 100644 --- a/playground/src/app/pages/listbox/examples/basic/basic.example.ts +++ b/playground/src/app/pages/listbox/examples/basic/basic.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; -import { Label, Listbox, ListboxModelValue, ListboxOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; +import { Label, Listbox, type ListboxModelValue, type ListboxOption } from '@sushi-kit/angular'; interface WorkspaceForm { workspace: ListboxModelValue; diff --git a/playground/src/app/pages/listbox/examples/lazy/lazy.example.ts b/playground/src/app/pages/listbox/examples/lazy/lazy.example.ts index a22835d4..30831174 100644 --- a/playground/src/app/pages/listbox/examples/lazy/lazy.example.ts +++ b/playground/src/app/pages/listbox/examples/lazy/lazy.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Badge, Label, Listbox, ListboxFooterTemplate, ListboxHeaderTemplate, ListboxOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, DestroyRef, inject, signal, type WritableSignal } from '@angular/core'; +import { Badge, Label, Listbox, ListboxFooterTemplate, ListboxHeaderTemplate, type ListboxOption } from '@sushi-kit/angular'; @Component({ selector: 'pg-listbox-lazy-example', @@ -20,9 +20,17 @@ export class ListboxLazyExample { protected readonly options: WritableSignal = signal(this.members.slice(0, 12)); protected readonly loading: WritableSignal = signal(false); + private readonly destroyRef: DestroyRef = inject(DestroyRef); + private timer: ReturnType | undefined; + + public constructor() { + this.destroyRef.onDestroy((): void => clearTimeout(this.timer)); + } + protected loadMore(offset: number): void { + if (this.loading()) return; this.loading.set(true); - window.setTimeout((): void => { + this.timer = setTimeout((): void => { this.options.set(this.members.slice(0, offset + this.pageSize)); this.loading.set(false); }, 500); diff --git a/playground/src/app/pages/listbox/examples/multiple/multiple.example.ts b/playground/src/app/pages/listbox/examples/multiple/multiple.example.ts index 2b0e7783..863a5070 100644 --- a/playground/src/app/pages/listbox/examples/multiple/multiple.example.ts +++ b/playground/src/app/pages/listbox/examples/multiple/multiple.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, computed, Signal, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; -import { Badge, Label, Listbox, ListboxModelValue, ListboxOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, computed, type Signal, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; +import { Badge, Label, Listbox, type ListboxModelValue, type ListboxOption } from '@sushi-kit/angular'; interface ChannelForm { channels: ListboxModelValue; diff --git a/playground/src/app/pages/listbox/examples/states/states.example.ts b/playground/src/app/pages/listbox/examples/states/states.example.ts index 8d2285e7..9fb679df 100644 --- a/playground/src/app/pages/listbox/examples/states/states.example.ts +++ b/playground/src/app/pages/listbox/examples/states/states.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Card, Label, Listbox, ListboxEmptyTemplate, ListboxOption } from '@sushi-kit/angular'; +import { Card, Label, Listbox, ListboxEmptyTemplate, type ListboxOption } from '@sushi-kit/angular'; @Component({ selector: 'pg-listbox-states-example', diff --git a/playground/src/app/pages/listbox/examples/templates/templates.example.ts b/playground/src/app/pages/listbox/examples/templates/templates.example.ts index 294343ac..92ff988e 100644 --- a/playground/src/app/pages/listbox/examples/templates/templates.example.ts +++ b/playground/src/app/pages/listbox/examples/templates/templates.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { Avatar, Badge, @@ -8,8 +8,8 @@ import { ListboxEmptyFilterTemplate, ListboxGroupTemplate, ListboxItemTemplate, - ListboxModelValue, - ListboxOption, + type ListboxModelValue, + type ListboxOption, } from '@sushi-kit/angular'; interface ReviewerOption extends ListboxOption { diff --git a/playground/src/app/pages/listbox/listbox.page.ts b/playground/src/app/pages/listbox/listbox.page.ts index 18a7ab8d..af4c8b8b 100644 --- a/playground/src/app/pages/listbox/listbox.page.ts +++ b/playground/src/app/pages/listbox/listbox.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { ListboxBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/mask/examples/gallery/gallery.example.ts b/playground/src/app/pages/mask/examples/gallery/gallery.example.ts index f5fb6ebc..664be9af 100644 --- a/playground/src/app/pages/mask/examples/gallery/gallery.example.ts +++ b/playground/src/app/pages/mask/examples/gallery/gallery.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Mask, MaskShape } from '@sushi-kit/angular'; +import { Mask, type MaskShape } from '@sushi-kit/angular'; @Component({ selector: 'pg-mask-gallery-example', diff --git a/playground/src/app/pages/mask/mask.page.ts b/playground/src/app/pages/mask/mask.page.ts index af8a07bf..8d3f6459 100644 --- a/playground/src/app/pages/mask/mask.page.ts +++ b/playground/src/app/pages/mask/mask.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import galleryHtml from './examples/gallery/gallery.example.html'; import * as galleryTs from './examples/gallery/gallery.example.ts' with { loader: 'text' }; import { MaskGalleryExample } from './examples/gallery/gallery.example'; diff --git a/playground/src/app/pages/menu/examples/active/active.example.ts b/playground/src/app/pages/menu/examples/active/active.example.ts index 04d92e30..4ce71fac 100644 --- a/playground/src/app/pages/menu/examples/active/active.example.ts +++ b/playground/src/app/pages/menu/examples/active/active.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, computed, Signal, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, type Signal, signal, type WritableSignal } from '@angular/core'; import { LucideColumns2, LucideEye, LucidePanelLeft } from '@lucide/angular'; -import { Label, Menu, MenuEntry, MenuItem, MenuItemTemplate } from '@sushi-kit/angular'; +import { Label, Menu, type MenuEntry, type MenuItem, MenuItemTemplate } from '@sushi-kit/angular'; type View = 'editor' | 'preview' | 'split'; diff --git a/playground/src/app/pages/menu/examples/basic/basic.example.ts b/playground/src/app/pages/menu/examples/basic/basic.example.ts index 0becb934..be6326fd 100644 --- a/playground/src/app/pages/menu/examples/basic/basic.example.ts +++ b/playground/src/app/pages/menu/examples/basic/basic.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Label, Menu, MenuEntry, MenuValue } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Label, Menu, type MenuEntry, type MenuValue } from '@sushi-kit/angular'; @Component({ selector: 'pg-menu-basic-example', diff --git a/playground/src/app/pages/menu/examples/context/context.example.ts b/playground/src/app/pages/menu/examples/context/context.example.ts index ac449b82..d184597a 100644 --- a/playground/src/app/pages/menu/examples/context/context.example.ts +++ b/playground/src/app/pages/menu/examples/context/context.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Card, ContextMenuTrigger, Kbd, Menu, MenuEntry, MenuValue } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Card, ContextMenuTrigger, Kbd, Menu, type MenuEntry, type MenuValue } from '@sushi-kit/angular'; @Component({ selector: 'pg-menu-context-example', diff --git a/playground/src/app/pages/menu/examples/groups/groups.example.ts b/playground/src/app/pages/menu/examples/groups/groups.example.ts index 53e903be..7af075e4 100644 --- a/playground/src/app/pages/menu/examples/groups/groups.example.ts +++ b/playground/src/app/pages/menu/examples/groups/groups.example.ts @@ -1,16 +1,16 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Avatar, Badge, Divider, Menu, MenuEndTemplate, - MenuEntry, + type MenuEntry, MenuGroupTemplate, - MenuItem, + type MenuItem, MenuItemTemplate, MenuStartTemplate, - MenuValue, + type MenuValue, } from '@sushi-kit/angular'; type WorkspaceAction = 'profile' | 'settings' | 'help' | 'sign-out'; diff --git a/playground/src/app/pages/menu/examples/popup/popup.example.ts b/playground/src/app/pages/menu/examples/popup/popup.example.ts index 49de96ab..f8975ebc 100644 --- a/playground/src/app/pages/menu/examples/popup/popup.example.ts +++ b/playground/src/app/pages/menu/examples/popup/popup.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideChevronDown } from '@lucide/angular'; -import { Button, Card, Join, JoinItem, Menu, MenuEntry, MenuTrigger, MenuValue } from '@sushi-kit/angular'; +import { Button, Card, Join, JoinItem, Menu, type MenuEntry, MenuTrigger, type MenuValue } from '@sushi-kit/angular'; @Component({ selector: 'pg-menu-popup-example', diff --git a/playground/src/app/pages/menu/examples/states/states.example.ts b/playground/src/app/pages/menu/examples/states/states.example.ts index c316bc2b..f53b7603 100644 --- a/playground/src/app/pages/menu/examples/states/states.example.ts +++ b/playground/src/app/pages/menu/examples/states/states.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Label, Menu, MenuEntry } from '@sushi-kit/angular'; +import { Label, Menu, type MenuEntry } from '@sushi-kit/angular'; @Component({ selector: 'pg-menu-states-example', diff --git a/playground/src/app/pages/menu/examples/submenu/submenu.example.ts b/playground/src/app/pages/menu/examples/submenu/submenu.example.ts index c6d88e8a..6455d1f5 100644 --- a/playground/src/app/pages/menu/examples/submenu/submenu.example.ts +++ b/playground/src/app/pages/menu/examples/submenu/submenu.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideChevronDown } from '@lucide/angular'; -import { Button, Menu, MenuEntry, MenuTrigger, MenuValue } from '@sushi-kit/angular'; +import { Button, Menu, type MenuEntry, MenuTrigger, type MenuValue } from '@sushi-kit/angular'; @Component({ selector: 'pg-menu-submenu-example', diff --git a/playground/src/app/pages/menu/menu.page.ts b/playground/src/app/pages/menu/menu.page.ts index 095ca9ac..40da50ed 100644 --- a/playground/src/app/pages/menu/menu.page.ts +++ b/playground/src/app/pages/menu/menu.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge, Kbd } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import activeHtml from './examples/active/active.example.html'; import * as activeTs from './examples/active/active.example.ts' with { loader: 'text' }; import { MenuActiveExample } from './examples/active/active.example'; diff --git a/playground/src/app/pages/message/message.page.ts b/playground/src/app/pages/message/message.page.ts index 0f3544e5..1ed815ad 100644 --- a/playground/src/app/pages/message/message.page.ts +++ b/playground/src/app/pages/message/message.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import actionsHtml from './examples/actions/actions.example.html'; import * as actionsTs from './examples/actions/actions.example.ts' with { loader: 'text' }; import { MessageActionsExample } from './examples/actions/actions.example'; diff --git a/playground/src/app/pages/multi-select/examples/basic/basic.example.ts b/playground/src/app/pages/multi-select/examples/basic/basic.example.ts index 6734335b..48b2f863 100644 --- a/playground/src/app/pages/multi-select/examples/basic/basic.example.ts +++ b/playground/src/app/pages/multi-select/examples/basic/basic.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField, minLength } from '@angular/forms/signals'; -import { Label, MultiSelect, MultiSelectModelValue, MultiSelectOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField, minLength } from '@angular/forms/signals'; +import { Label, MultiSelect, type MultiSelectModelValue, type MultiSelectOption } from '@sushi-kit/angular'; interface LabelForm { labels: MultiSelectModelValue; diff --git a/playground/src/app/pages/multi-select/examples/behavior/behavior.example.ts b/playground/src/app/pages/multi-select/examples/behavior/behavior.example.ts index 19482c1d..e48326f5 100644 --- a/playground/src/app/pages/multi-select/examples/behavior/behavior.example.ts +++ b/playground/src/app/pages/multi-select/examples/behavior/behavior.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Label, MultiSelect, MultiSelectModelValue, MultiSelectOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Label, MultiSelect, type MultiSelectModelValue, type MultiSelectOption } from '@sushi-kit/angular'; @Component({ selector: 'pg-multi-select-behavior-example', diff --git a/playground/src/app/pages/multi-select/examples/category-selection/category-selection.example.ts b/playground/src/app/pages/multi-select/examples/category-selection/category-selection.example.ts index 674b3952..e396c47b 100644 --- a/playground/src/app/pages/multi-select/examples/category-selection/category-selection.example.ts +++ b/playground/src/app/pages/multi-select/examples/category-selection/category-selection.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideCreditCard, LucideFolderKanban, LucideUsers } from '@lucide/angular'; import { Badge, @@ -9,9 +9,9 @@ import { MultiSelect, MultiSelectGroupTemplate, MultiSelectItemTemplate, - MultiSelectModelValue, - MultiSelectOption, - MultiSelectValue, + type MultiSelectModelValue, + type MultiSelectOption, + type MultiSelectValue, Progress, } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/multi-select/examples/groups/groups.example.ts b/playground/src/app/pages/multi-select/examples/groups/groups.example.ts index d4eefaa2..e7a8061f 100644 --- a/playground/src/app/pages/multi-select/examples/groups/groups.example.ts +++ b/playground/src/app/pages/multi-select/examples/groups/groups.example.ts @@ -1,15 +1,15 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideGlobe } from '@lucide/angular'; import { Badge, Label, MultiSelect, - MultiSelectCompareWith, + type MultiSelectCompareWith, MultiSelectGroupTemplate, MultiSelectItemTemplate, - MultiSelectModelValue, - MultiSelectOption, - MultiSelectValue, + type MultiSelectModelValue, + type MultiSelectOption, + type MultiSelectValue, } from '@sushi-kit/angular'; interface Country extends MultiSelectOption { diff --git a/playground/src/app/pages/multi-select/examples/selected/selected.example.ts b/playground/src/app/pages/multi-select/examples/selected/selected.example.ts index 8f983821..e93e69e2 100644 --- a/playground/src/app/pages/multi-select/examples/selected/selected.example.ts +++ b/playground/src/app/pages/multi-select/examples/selected/selected.example.ts @@ -1,15 +1,15 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Badge, Chip, Label, MultiSelect, - MultiSelectCompareWith, + type MultiSelectCompareWith, MultiSelectItemTemplate, - MultiSelectModelValue, - MultiSelectOption, + type MultiSelectModelValue, + type MultiSelectOption, MultiSelectSelectedItemsTemplate, - MultiSelectValue, + type MultiSelectValue, } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/multi-select/examples/states/states.example.ts b/playground/src/app/pages/multi-select/examples/states/states.example.ts index 4956d96d..8238fbff 100644 --- a/playground/src/app/pages/multi-select/examples/states/states.example.ts +++ b/playground/src/app/pages/multi-select/examples/states/states.example.ts @@ -1,5 +1,12 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Fieldset, FieldsetLegend, Label, MultiSelect, MultiSelectModelValue, MultiSelectOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { + Fieldset, + FieldsetLegend, + Label, + MultiSelect, + type MultiSelectModelValue, + type MultiSelectOption, +} from '@sushi-kit/angular'; @Component({ selector: 'pg-multi-select-states-example', diff --git a/playground/src/app/pages/multi-select/examples/templates/templates.example.ts b/playground/src/app/pages/multi-select/examples/templates/templates.example.ts index 021b5046..8dde7aae 100644 --- a/playground/src/app/pages/multi-select/examples/templates/templates.example.ts +++ b/playground/src/app/pages/multi-select/examples/templates/templates.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideBadgeCheck, LucideChevronsUpDown, LucideCircleX } from '@lucide/angular'; import { Badge, @@ -15,8 +15,8 @@ import { MultiSelectHeaderTemplate, MultiSelectLoadingIconTemplate, MultiSelectLoadingTemplate, - MultiSelectModelValue, - MultiSelectOption, + type MultiSelectModelValue, + type MultiSelectOption, Spinner, } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/multi-select/multi-select.page.ts b/playground/src/app/pages/multi-select/multi-select.page.ts index e178a6f1..c18792ef 100644 --- a/playground/src/app/pages/multi-select/multi-select.page.ts +++ b/playground/src/app/pages/multi-select/multi-select.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { MultiSelectBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/navbar/examples/basic/basic.example.ts b/playground/src/app/pages/navbar/examples/basic/basic.example.ts index 0d17230b..2637f2c6 100644 --- a/playground/src/app/pages/navbar/examples/basic/basic.example.ts +++ b/playground/src/app/pages/navbar/examples/basic/basic.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideSoup } from '@lucide/angular'; -import { Navbar, NavbarBrand, NavbarItem } from '@sushi-kit/angular'; +import { Navbar, NavbarBrand, type NavbarItem } from '@sushi-kit/angular'; type Page = 'Products' | 'Solutions' | 'Pricing'; diff --git a/playground/src/app/pages/navbar/examples/custom/custom.example.ts b/playground/src/app/pages/navbar/examples/custom/custom.example.ts index 15c57124..3262e159 100644 --- a/playground/src/app/pages/navbar/examples/custom/custom.example.ts +++ b/playground/src/app/pages/navbar/examples/custom/custom.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideBell, LucidePlus, LucideSearch, LucideZap } from '@lucide/angular'; import { Badge, @@ -7,7 +7,7 @@ import { Navbar, NavbarAction, NavbarBrand, - NavbarItem, + type NavbarItem, NavbarItemTemplate, Status, } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/navbar/examples/desktop/desktop.example.ts b/playground/src/app/pages/navbar/examples/desktop/desktop.example.ts index d5529eaf..ddcfb3ad 100644 --- a/playground/src/app/pages/navbar/examples/desktop/desktop.example.ts +++ b/playground/src/app/pages/navbar/examples/desktop/desktop.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, computed, Signal, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, type Signal, signal, type WritableSignal } from '@angular/core'; import { LucideChevronDown, LucideChevronRight, @@ -16,9 +16,9 @@ import { Button, Divider, Menu, - MenuEntry, + type MenuEntry, MenuTrigger, - MenuValue, + type MenuValue, Navbar, NavbarAction, NavbarBrand, diff --git a/playground/src/app/pages/navbar/examples/orientations/orientations.example.ts b/playground/src/app/pages/navbar/examples/orientations/orientations.example.ts index 2d56e4b3..be23d8bd 100644 --- a/playground/src/app/pages/navbar/examples/orientations/orientations.example.ts +++ b/playground/src/app/pages/navbar/examples/orientations/orientations.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Navbar, NavbarItem } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Navbar, type NavbarItem } from '@sushi-kit/angular'; type ProjectView = 'Board' | 'Timeline' | 'Reports'; type SettingsPage = 'Profile' | 'Notifications' | 'Security'; diff --git a/playground/src/app/pages/navbar/examples/submenus/submenus.example.ts b/playground/src/app/pages/navbar/examples/submenus/submenus.example.ts index ba649677..b6b6e20d 100644 --- a/playground/src/app/pages/navbar/examples/submenus/submenus.example.ts +++ b/playground/src/app/pages/navbar/examples/submenus/submenus.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideBox } from '@lucide/angular'; -import { Navbar, NavbarBrand, NavbarItem } from '@sushi-kit/angular'; +import { Navbar, NavbarBrand, type NavbarItem } from '@sushi-kit/angular'; type Destination = 'Products' | 'Analytics' | 'Automation' | 'Resources' | 'Documentation' | 'Changelog' | 'Pricing'; diff --git a/playground/src/app/pages/navbar/navbar.page.ts b/playground/src/app/pages/navbar/navbar.page.ts index 46e8b9f9..852e7a34 100644 --- a/playground/src/app/pages/navbar/navbar.page.ts +++ b/playground/src/app/pages/navbar/navbar.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import { NavbarBasicExample } from './examples/basic/basic.example'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; diff --git a/playground/src/app/pages/order-list/examples/basic/basic.example.ts b/playground/src/app/pages/order-list/examples/basic/basic.example.ts index 33ebd096..3067dd15 100644 --- a/playground/src/app/pages/order-list/examples/basic/basic.example.ts +++ b/playground/src/app/pages/order-list/examples/basic/basic.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; -import { Label, OrderList, OrderListOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; +import { Label, OrderList, type OrderListOption } from '@sushi-kit/angular'; interface WorkflowForm { steps: readonly OrderListOption[]; diff --git a/playground/src/app/pages/order-list/examples/drag-drop/drag-drop.example.ts b/playground/src/app/pages/order-list/examples/drag-drop/drag-drop.example.ts index 60dfaa0f..febe4b64 100644 --- a/playground/src/app/pages/order-list/examples/drag-drop/drag-drop.example.ts +++ b/playground/src/app/pages/order-list/examples/drag-drop/drag-drop.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Checkbox, Label, OrderList, OrderListItemTemplate, OrderListOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Checkbox, Label, OrderList, OrderListItemTemplate, type OrderListOption } from '@sushi-kit/angular'; @Component({ selector: 'pg-order-list-drag-drop-example', diff --git a/playground/src/app/pages/order-list/examples/states/states.example.ts b/playground/src/app/pages/order-list/examples/states/states.example.ts index 093dbcc3..936f0516 100644 --- a/playground/src/app/pages/order-list/examples/states/states.example.ts +++ b/playground/src/app/pages/order-list/examples/states/states.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { OrderList, OrderListOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { OrderList, type OrderListOption } from '@sushi-kit/angular'; @Component({ selector: 'pg-order-list-states-example', diff --git a/playground/src/app/pages/order-list/examples/templates/templates.example.ts b/playground/src/app/pages/order-list/examples/templates/templates.example.ts index 2abb8da3..e788509b 100644 --- a/playground/src/app/pages/order-list/examples/templates/templates.example.ts +++ b/playground/src/app/pages/order-list/examples/templates/templates.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideLockKeyhole, LucideLockKeyholeOpen, LucideSearch } from '@lucide/angular'; import { Badge, @@ -10,8 +10,8 @@ import { OrderListFilterTemplate, OrderListHeaderTemplate, OrderListItemTemplate, - OrderListOption, - OrderListValue, + type OrderListOption, + type OrderListValue, } from '@sushi-kit/angular'; interface BacklogItem extends OrderListOption { diff --git a/playground/src/app/pages/order-list/order-list.page.ts b/playground/src/app/pages/order-list/order-list.page.ts index 2b9cbcef..a2ccc969 100644 --- a/playground/src/app/pages/order-list/order-list.page.ts +++ b/playground/src/app/pages/order-list/order-list.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { OrderListBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/overview/examples/invite/invite.example.ts b/playground/src/app/pages/overview/examples/invite/invite.example.ts index 6d0505da..4c7a65c0 100644 --- a/playground/src/app/pages/overview/examples/invite/invite.example.ts +++ b/playground/src/app/pages/overview/examples/invite/invite.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideArrowLeft, LucideSend, LucideShieldCheck } from '@lucide/angular'; import { Button, @@ -8,11 +8,11 @@ import { Input, Label, Message, - NavbarItem, + type NavbarItem, Radio, Sidebar, SidebarFooter, - SidebarGroup, + type SidebarGroup, Toggle, } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/overview/overview.page.ts b/playground/src/app/pages/overview/overview.page.ts index 5acd49dd..d8081ff6 100644 --- a/playground/src/app/pages/overview/overview.page.ts +++ b/playground/src/app/pages/overview/overview.page.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Code, CodeLine, Tab, Tabs, TabsValue } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Code, CodeLine, Tab, Tabs, type TabsValue } from '@sushi-kit/angular'; import { highlightLines } from '../../shared/example-code/example-highlighter'; import { textSource } from '../../shared/example-code/example-source'; import inviteHtml from './examples/invite/invite.example.html'; diff --git a/playground/src/app/pages/pagination/examples/appearance/appearance.example.ts b/playground/src/app/pages/pagination/examples/appearance/appearance.example.ts index 2c902700..a1c04a6e 100644 --- a/playground/src/app/pages/pagination/examples/appearance/appearance.example.ts +++ b/playground/src/app/pages/pagination/examples/appearance/appearance.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Pagination, PaginationSeverity, PaginationSize, PaginationVariant } from '@sushi-kit/angular'; +import { Pagination, type PaginationSeverity, type PaginationSize, type PaginationVariant } from '@sushi-kit/angular'; interface PaginationStyle { readonly label: string; diff --git a/playground/src/app/pages/pagination/examples/basic/basic.example.ts b/playground/src/app/pages/pagination/examples/basic/basic.example.ts index 94441723..b01b32c9 100644 --- a/playground/src/app/pages/pagination/examples/basic/basic.example.ts +++ b/playground/src/app/pages/pagination/examples/basic/basic.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Pagination } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/pagination/examples/compact/compact.example.ts b/playground/src/app/pages/pagination/examples/compact/compact.example.ts index edb1e6ea..bd26a256 100644 --- a/playground/src/app/pages/pagination/examples/compact/compact.example.ts +++ b/playground/src/app/pages/pagination/examples/compact/compact.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Card, Pagination, PaginationLabels } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Card, Pagination, type PaginationLabels } from '@sushi-kit/angular'; @Component({ selector: 'pg-pagination-compact-example', diff --git a/playground/src/app/pages/pagination/pagination.page.ts b/playground/src/app/pages/pagination/pagination.page.ts index a689c880..f6060db1 100644 --- a/playground/src/app/pages/pagination/pagination.page.ts +++ b/playground/src/app/pages/pagination/pagination.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import appearanceHtml from './examples/appearance/appearance.example.html'; import * as appearanceTs from './examples/appearance/appearance.example.ts' with { loader: 'text' }; import { PaginationAppearanceExample } from './examples/appearance/appearance.example'; diff --git a/playground/src/app/pages/popover/examples/selection/selection.example.ts b/playground/src/app/pages/popover/examples/selection/selection.example.ts index e5895c70..79f14ae1 100644 --- a/playground/src/app/pages/popover/examples/selection/selection.example.ts +++ b/playground/src/app/pages/popover/examples/selection/selection.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideChefHat } from '@lucide/angular'; import { Button, Popover, PopoverTrigger } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/popover/popover.page.ts b/playground/src/app/pages/popover/popover.page.ts index f8efdea1..0504a458 100644 --- a/playground/src/app/pages/popover/popover.page.ts +++ b/playground/src/app/pages/popover/popover.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import html from './examples/usage/usage.example.html'; import * as ts from './examples/usage/usage.example.ts' with { loader: 'text' }; import { PopoverUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/progress/examples/animations/animations.example.ts b/playground/src/app/pages/progress/examples/animations/animations.example.ts index d5b5cc85..bf9916bc 100644 --- a/playground/src/app/pages/progress/examples/animations/animations.example.ts +++ b/playground/src/app/pages/progress/examples/animations/animations.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Badge, Button, Progress } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/progress/examples/usage/usage.example.ts b/playground/src/app/pages/progress/examples/usage/usage.example.ts index 38422803..3284f8c0 100644 --- a/playground/src/app/pages/progress/examples/usage/usage.example.ts +++ b/playground/src/app/pages/progress/examples/usage/usage.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { Button, Progress } from '@sushi-kit/angular'; @Component({ diff --git a/playground/src/app/pages/progress/progress.page.ts b/playground/src/app/pages/progress/progress.page.ts index b9b4df1d..6bc74d03 100644 --- a/playground/src/app/pages/progress/progress.page.ts +++ b/playground/src/app/pages/progress/progress.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import animationsHtml from './examples/animations/animations.example.html'; import * as animationsTs from './examples/animations/animations.example.ts' with { loader: 'text' }; import { ProgressAnimationsExample } from './examples/animations/animations.example'; diff --git a/playground/src/app/pages/radio/examples/usage/usage.example.ts b/playground/src/app/pages/radio/examples/usage/usage.example.ts index ff4e1d84..21fbc3be 100644 --- a/playground/src/app/pages/radio/examples/usage/usage.example.ts +++ b/playground/src/app/pages/radio/examples/usage/usage.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { Button, Card, Fieldset, FieldsetContent, FieldsetLegend, Label, Radio } from '@sushi-kit/angular'; interface PlanForm { diff --git a/playground/src/app/pages/radio/radio.page.ts b/playground/src/app/pages/radio/radio.page.ts index 27bef3d4..5fa2b3b5 100644 --- a/playground/src/app/pages/radio/radio.page.ts +++ b/playground/src/app/pages/radio/radio.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import usageHtml from './examples/usage/usage.example.html'; import * as usageTs from './examples/usage/usage.example.ts' with { loader: 'text' }; import { RadioUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/range/examples/basic/basic.example.ts b/playground/src/app/pages/range/examples/basic/basic.example.ts index 3cbb1b6b..9780c229 100644 --- a/playground/src/app/pages/range/examples/basic/basic.example.ts +++ b/playground/src/app/pages/range/examples/basic/basic.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField, max, min, SchemaPathTree } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField, max, min, type SchemaPathTree } from '@angular/forms/signals'; import { Button, Label, Range } from '@sushi-kit/angular'; interface VolumeModel { diff --git a/playground/src/app/pages/range/examples/mixer/mixer.example.ts b/playground/src/app/pages/range/examples/mixer/mixer.example.ts index 4e59369a..d8c277b2 100644 --- a/playground/src/app/pages/range/examples/mixer/mixer.example.ts +++ b/playground/src/app/pages/range/examples/mixer/mixer.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { Label, Range } from '@sushi-kit/angular'; interface MixerModel { diff --git a/playground/src/app/pages/range/range.page.ts b/playground/src/app/pages/range/range.page.ts index e68ccd28..7e514786 100644 --- a/playground/src/app/pages/range/range.page.ts +++ b/playground/src/app/pages/range/range.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import appearanceHtml from './examples/appearance/appearance.example.html'; import * as appearanceTs from './examples/appearance/appearance.example.ts' with { loader: 'text' }; import { RangeAppearanceExample } from './examples/appearance/appearance.example'; diff --git a/playground/src/app/pages/select-button/examples/basic/basic.example.ts b/playground/src/app/pages/select-button/examples/basic/basic.example.ts index 8108ae48..18b4a346 100644 --- a/playground/src/app/pages/select-button/examples/basic/basic.example.ts +++ b/playground/src/app/pages/select-button/examples/basic/basic.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; -import { Button, Card, Label, SelectButton, SelectButtonOption, SelectButtonValue } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; +import { Button, Card, Label, SelectButton, type SelectButtonOption, type SelectButtonValue } from '@sushi-kit/angular'; interface AlignmentForm { alignment: SelectButtonValue | null; diff --git a/playground/src/app/pages/select-button/examples/layout/layout.example.ts b/playground/src/app/pages/select-button/examples/layout/layout.example.ts index f11a7291..d985816d 100644 --- a/playground/src/app/pages/select-button/examples/layout/layout.example.ts +++ b/playground/src/app/pages/select-button/examples/layout/layout.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Label, SelectButton, SelectButtonOption, SelectButtonValue } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Label, SelectButton, type SelectButtonOption, type SelectButtonValue } from '@sushi-kit/angular'; @Component({ selector: 'pg-select-button-layout-example', diff --git a/playground/src/app/pages/select-button/examples/template/template.example.ts b/playground/src/app/pages/select-button/examples/template/template.example.ts index 41c027a0..27cda4c8 100644 --- a/playground/src/app/pages/select-button/examples/template/template.example.ts +++ b/playground/src/app/pages/select-button/examples/template/template.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideGrid3x3, LucideList, LucideRows3 } from '@lucide/angular'; -import { SelectButton, SelectButtonOption, SelectButtonOptionTemplate, SelectButtonValue } from '@sushi-kit/angular'; +import { SelectButton, type SelectButtonOption, SelectButtonOptionTemplate, type SelectButtonValue } from '@sushi-kit/angular'; @Component({ selector: 'pg-select-button-template-example', diff --git a/playground/src/app/pages/select-button/select-button.page.ts b/playground/src/app/pages/select-button/select-button.page.ts index 6c692025..136ccb45 100644 --- a/playground/src/app/pages/select-button/select-button.page.ts +++ b/playground/src/app/pages/select-button/select-button.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { SelectButtonBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/select/examples/basic/basic.example.ts b/playground/src/app/pages/select/examples/basic/basic.example.ts index d8726541..717f1c07 100644 --- a/playground/src/app/pages/select/examples/basic/basic.example.ts +++ b/playground/src/app/pages/select/examples/basic/basic.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField, required } from '@angular/forms/signals'; -import { Label, Select, SelectModelValue, SelectOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField, required } from '@angular/forms/signals'; +import { Label, Select, type SelectModelValue, type SelectOption } from '@sushi-kit/angular'; interface ColorForm { color: SelectModelValue; diff --git a/playground/src/app/pages/select/examples/behavior/behavior.example.ts b/playground/src/app/pages/select/examples/behavior/behavior.example.ts index 1e570858..486a75eb 100644 --- a/playground/src/app/pages/select/examples/behavior/behavior.example.ts +++ b/playground/src/app/pages/select/examples/behavior/behavior.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Label, Select, SelectModelValue, SelectOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Label, Select, type SelectModelValue, type SelectOption } from '@sushi-kit/angular'; @Component({ selector: 'pg-select-behavior-example', diff --git a/playground/src/app/pages/select/examples/options/options.example.ts b/playground/src/app/pages/select/examples/options/options.example.ts index 7e8ae398..3d207dc3 100644 --- a/playground/src/app/pages/select/examples/options/options.example.ts +++ b/playground/src/app/pages/select/examples/options/options.example.ts @@ -1,16 +1,16 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideGlobe } from '@lucide/angular'; import { Badge, Label, Select, - SelectCompareWith, + type SelectCompareWith, SelectGroupTemplate, SelectItemTemplate, - SelectModelValue, - SelectOption, + type SelectModelValue, + type SelectOption, SelectSelectedItemTemplate, - SelectValue, + type SelectValue, } from '@sushi-kit/angular'; interface Country extends SelectOption { diff --git a/playground/src/app/pages/select/examples/states/states.example.ts b/playground/src/app/pages/select/examples/states/states.example.ts index c1136b52..cdc2e915 100644 --- a/playground/src/app/pages/select/examples/states/states.example.ts +++ b/playground/src/app/pages/select/examples/states/states.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Fieldset, FieldsetLegend, Label, Select, SelectModelValue, SelectOption } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Fieldset, FieldsetLegend, Label, Select, type SelectModelValue, type SelectOption } from '@sushi-kit/angular'; @Component({ selector: 'pg-select-states-example', diff --git a/playground/src/app/pages/select/examples/templates/templates.example.ts b/playground/src/app/pages/select/examples/templates/templates.example.ts index 8233ecac..d07d7fbe 100644 --- a/playground/src/app/pages/select/examples/templates/templates.example.ts +++ b/playground/src/app/pages/select/examples/templates/templates.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideBadgeCheck, LucideChevronsUpDown, LucideCircleX } from '@lucide/angular'; import { Button, @@ -15,8 +15,8 @@ import { SelectHeaderTemplate, SelectLoadingIconTemplate, SelectLoadingTemplate, - SelectModelValue, - SelectOption, + type SelectModelValue, + type SelectOption, Spinner, } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/select/select.page.ts b/playground/src/app/pages/select/select.page.ts index c7433ecb..6d36844f 100644 --- a/playground/src/app/pages/select/select.page.ts +++ b/playground/src/app/pages/select/select.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { SelectBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/sidebar/examples/workspace/workspace.example.ts b/playground/src/app/pages/sidebar/examples/workspace/workspace.example.ts index 29813fc5..dc01b4b7 100644 --- a/playground/src/app/pages/sidebar/examples/workspace/workspace.example.ts +++ b/playground/src/app/pages/sidebar/examples/workspace/workspace.example.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { LucideBell, LucideChartNoAxesCombined, @@ -25,10 +25,10 @@ import { Navbar, NavbarAction, NavbarBrand, - NavbarItem, + type NavbarItem, Sidebar, SidebarFooter, - SidebarGroup, + type SidebarGroup, SidebarItemTemplate, } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/sidebar/sidebar.page.ts b/playground/src/app/pages/sidebar/sidebar.page.ts index d498f7c1..a66226b5 100644 --- a/playground/src/app/pages/sidebar/sidebar.page.ts +++ b/playground/src/app/pages/sidebar/sidebar.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge, Code, CodeLine } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import { highlightLines } from '../../shared/example-code/example-highlighter'; import { SidebarWorkspaceExample } from './examples/workspace/workspace.example'; import workspaceHtml from './examples/workspace/workspace.example.html'; diff --git a/playground/src/app/pages/skeleton/skeleton.page.ts b/playground/src/app/pages/skeleton/skeleton.page.ts index e5b31280..fc3fb643 100644 --- a/playground/src/app/pages/skeleton/skeleton.page.ts +++ b/playground/src/app/pages/skeleton/skeleton.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import html from './examples/usage/usage.example.html'; import * as ts from './examples/usage/usage.example.ts' with { loader: 'text' }; import { SkeletonUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/spinner/examples/contexts/contexts.example.ts b/playground/src/app/pages/spinner/examples/contexts/contexts.example.ts index 9c24f630..dfff043e 100644 --- a/playground/src/app/pages/spinner/examples/contexts/contexts.example.ts +++ b/playground/src/app/pages/spinner/examples/contexts/contexts.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Button, Card, CardTitle, List, ListItemTemplate, Spinner, SpinnerType } from '@sushi-kit/angular'; +import { Button, Card, CardTitle, List, ListItemTemplate, Spinner, type SpinnerType } from '@sushi-kit/angular'; interface Activity { readonly label: string; diff --git a/playground/src/app/pages/spinner/examples/usage/usage.example.ts b/playground/src/app/pages/spinner/examples/usage/usage.example.ts index 3399d0e0..23a8622d 100644 --- a/playground/src/app/pages/spinner/examples/usage/usage.example.ts +++ b/playground/src/app/pages/spinner/examples/usage/usage.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Spinner, SpinnerSize, SpinnerType } from '@sushi-kit/angular'; +import { Spinner, type SpinnerSize, type SpinnerType } from '@sushi-kit/angular'; @Component({ selector: 'pg-spinner-usage-example', diff --git a/playground/src/app/pages/spinner/spinner.page.ts b/playground/src/app/pages/spinner/spinner.page.ts index 14d36b7c..589f21ca 100644 --- a/playground/src/app/pages/spinner/spinner.page.ts +++ b/playground/src/app/pages/spinner/spinner.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import contextsHtml from './examples/contexts/contexts.example.html'; import * as contextsTs from './examples/contexts/contexts.example.ts' with { loader: 'text' }; import { SpinnerContextsExample } from './examples/contexts/contexts.example'; diff --git a/playground/src/app/pages/status/examples/services/services.example.ts b/playground/src/app/pages/status/examples/services/services.example.ts index 1e51a5a0..84e26d32 100644 --- a/playground/src/app/pages/status/examples/services/services.example.ts +++ b/playground/src/app/pages/status/examples/services/services.example.ts @@ -1,5 +1,14 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Card, CardTitle, List, ListItemTemplate, Status, StatusAnimation, StatusSeverity } from '@sushi-kit/angular'; +import { + Badge, + Card, + CardTitle, + List, + ListItemTemplate, + Status, + type StatusAnimation, + type StatusSeverity, +} from '@sushi-kit/angular'; interface Service { readonly name: string; diff --git a/playground/src/app/pages/status/status.page.ts b/playground/src/app/pages/status/status.page.ts index 882b1e96..2d785879 100644 --- a/playground/src/app/pages/status/status.page.ts +++ b/playground/src/app/pages/status/status.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import servicesHtml from './examples/services/services.example.html'; import * as servicesTs from './examples/services/services.example.ts' with { loader: 'text' }; import { StatusServicesExample } from './examples/services/services.example'; diff --git a/playground/src/app/pages/styling/styling.page.ts b/playground/src/app/pages/styling/styling.page.ts index 2a6a8c37..33dd2f7e 100644 --- a/playground/src/app/pages/styling/styling.page.ts +++ b/playground/src/app/pages/styling/styling.page.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, computed, input, InputSignal, Signal } from '@angular/core'; -import { Badge, Card, CardTitle, Code, CodeLine, Table, TableCellTemplate, TableColumn } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, computed, input, type InputSignal, type Signal } from '@angular/core'; +import { Badge, Card, CardTitle, Code, CodeLine, Table, TableCellTemplate, type TableColumn } from '@sushi-kit/angular'; import { apiReferences } from '../../generated/api-reference.generated'; import type { ApiMember, ApiReferenceData, ApiStyleProperty, ApiTemplate } from '../../shared/api-reference/api-reference.types'; @@ -15,7 +15,7 @@ interface StyleTemplate extends ApiTemplate { readonly selector: string; } -const appearanceNames: ReadonlySet = new Set([ +const appearanceNames: ReadonlySet = new Set([ 'animation', 'bordered', 'buttonShape', diff --git a/playground/src/app/pages/table/examples/appearance/appearance.example.ts b/playground/src/app/pages/table/examples/appearance/appearance.example.ts index 03c199b3..8af77cda 100644 --- a/playground/src/app/pages/table/examples/appearance/appearance.example.ts +++ b/playground/src/app/pages/table/examples/appearance/appearance.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Table, TableColumn } from '@sushi-kit/angular'; +import { Table, type TableColumn } from '@sushi-kit/angular'; interface Member { readonly name: string; diff --git a/playground/src/app/pages/table/examples/basic/basic.example.ts b/playground/src/app/pages/table/examples/basic/basic.example.ts index 2b561634..91f746c2 100644 --- a/playground/src/app/pages/table/examples/basic/basic.example.ts +++ b/playground/src/app/pages/table/examples/basic/basic.example.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, computed, signal, Signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; -import { Input, Label, Pagination, Table, TableColumn, TableSort } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, computed, signal, type Signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; +import { Input, Label, Pagination, Table, type TableColumn, type TableSort } from '@sushi-kit/angular'; interface InvoiceFilters { query: string; diff --git a/playground/src/app/pages/table/examples/mobile/mobile.example.ts b/playground/src/app/pages/table/examples/mobile/mobile.example.ts index 0f54426f..ecab6d64 100644 --- a/playground/src/app/pages/table/examples/mobile/mobile.example.ts +++ b/playground/src/app/pages/table/examples/mobile/mobile.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Table, TableCellTemplate, TableColumn } from '@sushi-kit/angular'; +import { Badge, Table, TableCellTemplate, type TableColumn } from '@sushi-kit/angular'; type ReleaseState = 'Ready' | 'Review' | 'Blocked'; diff --git a/playground/src/app/pages/table/examples/simple/simple.example.ts b/playground/src/app/pages/table/examples/simple/simple.example.ts index a92d7e2d..c8ce77df 100644 --- a/playground/src/app/pages/table/examples/simple/simple.example.ts +++ b/playground/src/app/pages/table/examples/simple/simple.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Table, TableColumn } from '@sushi-kit/angular'; +import { Table, type TableColumn } from '@sushi-kit/angular'; interface Product { readonly code: string; diff --git a/playground/src/app/pages/table/examples/states/states.example.ts b/playground/src/app/pages/table/examples/states/states.example.ts index 894ffda3..5c698771 100644 --- a/playground/src/app/pages/table/examples/states/states.example.ts +++ b/playground/src/app/pages/table/examples/states/states.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Button, Table, TableColumn } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Button, Table, type TableColumn } from '@sushi-kit/angular'; interface Deployment { readonly environment: string; diff --git a/playground/src/app/pages/table/examples/templates/templates.example.ts b/playground/src/app/pages/table/examples/templates/templates.example.ts index 1cf94b84..64fa3a15 100644 --- a/playground/src/app/pages/table/examples/templates/templates.example.ts +++ b/playground/src/app/pages/table/examples/templates/templates.example.ts @@ -4,7 +4,7 @@ import { Badge, Table, TableCaptionTemplate, - TableColumn, + type TableColumn, TableEmptyTemplate, TableFooterTemplate, TableHeaderTemplate, diff --git a/playground/src/app/pages/table/table.page.ts b/playground/src/app/pages/table/table.page.ts index e23fb683..a5a48238 100644 --- a/playground/src/app/pages/table/table.page.ts +++ b/playground/src/app/pages/table/table.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import appearanceHtml from './examples/appearance/appearance.example.html'; import * as appearanceTs from './examples/appearance/appearance.example.ts' with { loader: 'text' }; import { TableAppearanceExample } from './examples/appearance/appearance.example'; diff --git a/playground/src/app/pages/tabs/examples/basic/basic.example.ts b/playground/src/app/pages/tabs/examples/basic/basic.example.ts index 82d4b773..2e004139 100644 --- a/playground/src/app/pages/tabs/examples/basic/basic.example.ts +++ b/playground/src/app/pages/tabs/examples/basic/basic.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Tab, Tabs, TabsValue } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Tab, Tabs, type TabsValue } from '@sushi-kit/angular'; @Component({ selector: 'pg-tabs-basic-example', diff --git a/playground/src/app/pages/tabs/examples/behavior/behavior.example.ts b/playground/src/app/pages/tabs/examples/behavior/behavior.example.ts index eb2ec5fc..3a00debd 100644 --- a/playground/src/app/pages/tabs/examples/behavior/behavior.example.ts +++ b/playground/src/app/pages/tabs/examples/behavior/behavior.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Tab, Tabs, TabsValue } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Tab, Tabs, type TabsValue } from '@sushi-kit/angular'; @Component({ selector: 'pg-tabs-behavior-example', diff --git a/playground/src/app/pages/tabs/examples/variants/variants.example.ts b/playground/src/app/pages/tabs/examples/variants/variants.example.ts index eca46473..5ee14299 100644 --- a/playground/src/app/pages/tabs/examples/variants/variants.example.ts +++ b/playground/src/app/pages/tabs/examples/variants/variants.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { Tab, Tabs, TabsValue, TabsVariant } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { Tab, Tabs, type TabsValue, type TabsVariant } from '@sushi-kit/angular'; @Component({ selector: 'pg-tabs-variants-example', diff --git a/playground/src/app/pages/tabs/tabs.page.ts b/playground/src/app/pages/tabs/tabs.page.ts index b5c0a756..0b35fd63 100644 --- a/playground/src/app/pages/tabs/tabs.page.ts +++ b/playground/src/app/pages/tabs/tabs.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import basicHtml from './examples/basic/basic.example.html'; import * as basicTs from './examples/basic/basic.example.ts' with { loader: 'text' }; import { TabsBasicExample } from './examples/basic/basic.example'; diff --git a/playground/src/app/pages/textarea/examples/appearance/appearance.example.ts b/playground/src/app/pages/textarea/examples/appearance/appearance.example.ts index 6b1a9c2d..b7129014 100644 --- a/playground/src/app/pages/textarea/examples/appearance/appearance.example.ts +++ b/playground/src/app/pages/textarea/examples/appearance/appearance.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Fieldset, FieldsetLegend, Label, Textarea, TextareaSeverity, TextareaSize } from '@sushi-kit/angular'; +import { Badge, Fieldset, FieldsetLegend, Label, Textarea, type TextareaSeverity, type TextareaSize } from '@sushi-kit/angular'; @Component({ selector: 'pg-textarea-appearance-example', diff --git a/playground/src/app/pages/textarea/examples/basic/basic.example.ts b/playground/src/app/pages/textarea/examples/basic/basic.example.ts index f5ae2fbd..e6a8f321 100644 --- a/playground/src/app/pages/textarea/examples/basic/basic.example.ts +++ b/playground/src/app/pages/textarea/examples/basic/basic.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField, maxLength, required, SchemaPathTree } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField, maxLength, required, type SchemaPathTree } from '@angular/forms/signals'; import { Button, Join, JoinItem, Label, Textarea } from '@sushi-kit/angular'; interface TextareaModel { diff --git a/playground/src/app/pages/textarea/examples/resize/resize.example.ts b/playground/src/app/pages/textarea/examples/resize/resize.example.ts index dec8cc05..085253e3 100644 --- a/playground/src/app/pages/textarea/examples/resize/resize.example.ts +++ b/playground/src/app/pages/textarea/examples/resize/resize.example.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Badge, Fieldset, FieldsetLegend, Label, Textarea, TextareaResize } from '@sushi-kit/angular'; +import { Badge, Fieldset, FieldsetLegend, Label, Textarea, type TextareaResize } from '@sushi-kit/angular'; @Component({ selector: 'pg-textarea-resize-example', diff --git a/playground/src/app/pages/textarea/textarea.page.ts b/playground/src/app/pages/textarea/textarea.page.ts index 184f13d9..a4984e09 100644 --- a/playground/src/app/pages/textarea/textarea.page.ts +++ b/playground/src/app/pages/textarea/textarea.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import appearanceHtml from './examples/appearance/appearance.example.html'; import * as appearanceTs from './examples/appearance/appearance.example.ts' with { loader: 'text' }; import { TextareaAppearanceExample } from './examples/appearance/appearance.example'; diff --git a/playground/src/app/pages/toast/toast.page.ts b/playground/src/app/pages/toast/toast.page.ts index d983b48d..5cb014ee 100644 --- a/playground/src/app/pages/toast/toast.page.ts +++ b/playground/src/app/pages/toast/toast.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import usageHtml from './examples/usage/usage.example.html'; import * as usageTs from './examples/usage/usage.example.ts' with { loader: 'text' }; import { ToastUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/toggle-button/examples/usage/usage.example.ts b/playground/src/app/pages/toggle-button/examples/usage/usage.example.ts index 765785d5..4e3f890a 100644 --- a/playground/src/app/pages/toggle-button/examples/usage/usage.example.ts +++ b/playground/src/app/pages/toggle-button/examples/usage/usage.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { LucideCheck, LucideX } from '@lucide/angular'; import { Button, ToggleButton, ToggleButtonOffTemplate, ToggleButtonOnTemplate, ToggleButtonTemplate } from '@sushi-kit/angular'; diff --git a/playground/src/app/pages/toggle-button/toggle-button.page.ts b/playground/src/app/pages/toggle-button/toggle-button.page.ts index d849d61f..aa1a10c0 100644 --- a/playground/src/app/pages/toggle-button/toggle-button.page.ts +++ b/playground/src/app/pages/toggle-button/toggle-button.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import usageHtml from './examples/usage/usage.example.html'; import * as usageTs from './examples/usage/usage.example.ts' with { loader: 'text' }; import { ToggleButtonUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/toggle/examples/usage/usage.example.ts b/playground/src/app/pages/toggle/examples/usage/usage.example.ts index ddf5b537..689b0e64 100644 --- a/playground/src/app/pages/toggle/examples/usage/usage.example.ts +++ b/playground/src/app/pages/toggle/examples/usage/usage.example.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, signal, WritableSignal } from '@angular/core'; -import { FieldTree, form, FormField } from '@angular/forms/signals'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; +import { type FieldTree, form, FormField } from '@angular/forms/signals'; import { Button, Card, Label, Toggle } from '@sushi-kit/angular'; interface NotificationForm { diff --git a/playground/src/app/pages/toggle/toggle.page.ts b/playground/src/app/pages/toggle/toggle.page.ts index ef50ff43..782b2528 100644 --- a/playground/src/app/pages/toggle/toggle.page.ts +++ b/playground/src/app/pages/toggle/toggle.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import usageHtml from './examples/usage/usage.example.html'; import * as usageTs from './examples/usage/usage.example.ts' with { loader: 'text' }; import { ToggleUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/pages/tooltip/tooltip.page.ts b/playground/src/app/pages/tooltip/tooltip.page.ts index 2d42c524..d5edf96f 100644 --- a/playground/src/app/pages/tooltip/tooltip.page.ts +++ b/playground/src/app/pages/tooltip/tooltip.page.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Badge } from '@sushi-kit/angular'; import { ExampleCode } from '../../shared/example-code/example-code.component'; import { ExamplePreview } from '../../shared/example-code/example-preview.directive'; -import { ExampleSource, textSource } from '../../shared/example-code/example-source'; +import { type ExampleSource, textSource } from '../../shared/example-code/example-source'; import html from './examples/usage/usage.example.html'; import * as ts from './examples/usage/usage.example.ts' with { loader: 'text' }; import { TooltipUsageExample } from './examples/usage/usage.example'; diff --git a/playground/src/app/shared/api-reference/api-reference.component.ts b/playground/src/app/shared/api-reference/api-reference.component.ts index 36621a86..1ddc3ff0 100644 --- a/playground/src/app/shared/api-reference/api-reference.component.ts +++ b/playground/src/app/shared/api-reference/api-reference.component.ts @@ -1,6 +1,6 @@ import { DOCUMENT, NgTemplateOutlet } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, inject, input, InputSignal, Signal } from '@angular/core'; -import { Badge, List, ListItemTemplate, Table, TableCellTemplate, TableColumn } from '@sushi-kit/angular'; +import { ChangeDetectionStrategy, Component, computed, inject, input, type InputSignal, type Signal } from '@angular/core'; +import { Badge, List, ListItemTemplate, Table, TableCellTemplate, type TableColumn } from '@sushi-kit/angular'; import type { ApiMember, ApiMemberKind, @@ -119,7 +119,6 @@ export class ApiReference { protected readonly typeColumns: readonly TableColumn[] = typeColumns; protected readonly typeParameterColumns: readonly TableColumn[] = typeParameterColumns; - // Data prepared for the view. protected readonly groups: Signal = computed(() => (Object.keys(labels) as ApiMemberKind[]) .map((kind: ApiMemberKind): ApiGroup => ({ @@ -141,7 +140,6 @@ export class ApiReference { private readonly document: Document = inject(DOCUMENT); - // Stable targets used by type links and browser fragments. protected typeId(name: string): string { return `api-${this.api().className}-type-${name}`; } diff --git a/playground/src/app/shared/example-code/example-code.component.ts b/playground/src/app/shared/example-code/example-code.component.ts index 8963f7ef..07bb2446 100644 --- a/playground/src/app/shared/example-code/example-code.component.ts +++ b/playground/src/app/shared/example-code/example-code.component.ts @@ -4,15 +4,15 @@ import { computed, contentChild, input, - InputSignal, + type InputSignal, linkedSignal, - Signal, + type Signal, TemplateRef, ViewEncapsulation, - WritableSignal, + type WritableSignal, } from '@angular/core'; import { NgTemplateOutlet } from '@angular/common'; -import { Code, CodeLine, Tab, Tabs, TabsValue } from '@sushi-kit/angular'; +import { Code, CodeLine, Tab, Tabs, type TabsValue } from '@sushi-kit/angular'; import { highlightLines } from './example-highlighter'; import { ExamplePreview } from './example-preview.directive'; diff --git a/playground/src/app/shared/example-code/example-highlighter.ts b/playground/src/app/shared/example-code/example-highlighter.ts index 62757b23..b738a623 100644 --- a/playground/src/app/shared/example-code/example-highlighter.ts +++ b/playground/src/app/shared/example-code/example-highlighter.ts @@ -37,21 +37,24 @@ export const highlightLines: (source: string, language: CodeLanguage) => readonl }; const splitLines: (source: string) => readonly string[] = (source: string): readonly string[] => { - const lines: string[] = ['']; + const lines: string[] = []; const spans: string[] = []; const tokens: readonly string[] = source.split(/(|<\/span>|\n)/); + let line: string = ''; tokens.forEach((token: string): void => { if (token === '\n') { - lines[lines.length - 1] += ''.repeat(spans.length); - lines.push(spans.join('')); + lines.push(line + ''.repeat(spans.length)); + line = spans.join(''); return; } if (token.startsWith('') spans.pop(); - lines[lines.length - 1] += token; + line += token; }); + lines.push(line); + return lines; }; diff --git a/playground/src/app/shared/example-code/example-source.ts b/playground/src/app/shared/example-code/example-source.ts index a4902aff..75e66398 100644 --- a/playground/src/app/shared/example-code/example-source.ts +++ b/playground/src/app/shared/example-code/example-source.ts @@ -1,3 +1,8 @@ export type ExampleSource = Readonly<{ css?: string; html: string; typescript: string }>; -export const textSource: (module: unknown) => string = (module: unknown): string => (module as { default: string }).default; +export function textSource(module: unknown): string { + if (typeof module !== 'object' || module === null || !('default' in module) || typeof module.default !== 'string') { + throw new Error('Expected a text module with a default string export.'); + } + return module.default; +} diff --git a/playground/src/app/shared/markdown-document/markdown-document.component.ts b/playground/src/app/shared/markdown-document/markdown-document.component.ts index 01ae2885..4afed848 100644 --- a/playground/src/app/shared/markdown-document/markdown-document.component.ts +++ b/playground/src/app/shared/markdown-document/markdown-document.component.ts @@ -2,10 +2,10 @@ import { ChangeDetectionStrategy, Component, input, - InputSignal, + type InputSignal, output, - OutputEmitterRef, - ResourceRef, + type OutputEmitterRef, + type ResourceRef, resource, } from '@angular/core'; import { @@ -13,9 +13,9 @@ import { CodeLine, Spinner, Table, - TableAlignment, + type TableAlignment, TableCellTemplate, - TableColumn, + type TableColumn, TableHeaderTemplate, } from '@sushi-kit/angular'; import { marked } from 'marked'; diff --git a/playground/src/package-styles.css b/playground/src/package-styles.css index 09bbb49a..5869657c 100644 --- a/playground/src/package-styles.css +++ b/playground/src/package-styles.css @@ -1,8 +1,8 @@ @layer theme, base, components, utilities; -@import 'tailwindcss/theme.css' layer(theme); -@import 'tailwindcss/preflight.css' layer(base); -@import '../../dist/sushi/styles.css'; -@import 'tailwindcss/utilities.css' layer(utilities) source(none); +@import "tailwindcss/theme.css" layer(theme); +@import "tailwindcss/preflight.css" layer(base); +@import "../../dist/sushi/styles.css"; +@import "tailwindcss/utilities.css" layer(utilities) source(none); @source './app'; diff --git a/playground/src/playground.source.css b/playground/src/playground.source.css index ebf0455e..5da53bff 100644 --- a/playground/src/playground.source.css +++ b/playground/src/playground.source.css @@ -1,6 +1,6 @@ -@import '../../sushi/src/styles/sushi.source.css'; -@import 'tailwindcss/preflight.css' layer(base); +@import "../../sushi/src/styles/sushi.source.css"; +@import "tailwindcss/preflight.css" layer(base); @source './app'; -@import './styles.css'; +@import "./styles.css"; diff --git a/sushi/src/lib/accordion/accordion-item.directive.ts b/sushi/src/lib/accordion/accordion-item.directive.ts index 17be9e5d..0fa622ea 100644 --- a/sushi/src/lib/accordion/accordion-item.directive.ts +++ b/sushi/src/lib/accordion/accordion-item.directive.ts @@ -1,5 +1,13 @@ -import { booleanAttribute, Directive, inject, input, InputSignal, InputSignalWithTransform, TemplateRef } from '@angular/core'; -import { AccordionItemContext, AccordionValue } from './accordion.interfaces'; +import { + booleanAttribute, + Directive, + inject, + input, + type InputSignal, + type InputSignalWithTransform, + TemplateRef, +} from '@angular/core'; +import { type AccordionItemContext, type AccordionValue } from './accordion.interfaces'; /** Defines one accordion heading, value, and content template. */ @Directive({ selector: 'ng-template[suiAccordionItem]' }) diff --git a/sushi/src/lib/accordion/accordion.component.css b/sushi/src/lib/accordion/accordion.component.css index 881adac1..f67d30b9 100644 --- a/sushi/src/lib/accordion/accordion.component.css +++ b/sushi/src/lib/accordion/accordion.component.css @@ -27,13 +27,13 @@ --sui-accordion-motion-easing: cubic-bezier(0.4, 0, 0.2, 1); } -:host([data-size='md']) { +:host([data-size="md"]) { --_sui-accordion-trigger-min-height: 3rem; --_sui-accordion-trigger-padding-block: 0.75rem; --_sui-accordion-padding-inline: 1rem; } -:host([data-size='lg']) { +:host([data-size="lg"]) { --_sui-accordion-trigger-min-height: 3.5rem; --_sui-accordion-trigger-padding-block: 0.875rem; --_sui-accordion-padding-inline: 1.125rem; @@ -95,7 +95,7 @@ font-size: 1.125rem; } -.sui-accordion__trigger[aria-disabled='true'] { +.sui-accordion__trigger[aria-disabled="true"] { cursor: not-allowed; opacity: 0.5; } diff --git a/sushi/src/lib/accordion/accordion.component.ts b/sushi/src/lib/accordion/accordion.component.ts index a0b3c155..b254794b 100644 --- a/sushi/src/lib/accordion/accordion.component.ts +++ b/sushi/src/lib/accordion/accordion.component.ts @@ -12,16 +12,22 @@ import { contentChild, contentChildren, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, - Signal, + type ModelSignal, + type Signal, TemplateRef, } from '@angular/core'; import { LucideChevronDown } from '@lucide/angular'; import { AccordionItem } from './accordion-item.directive'; -import { AccordionItemContext, AccordionItemData, AccordionSize, AccordionValue, AccordionVariant } from './accordion.interfaces'; +import { + type AccordionItemContext, + type AccordionItemData, + type AccordionSize, + type AccordionValue, + type AccordionVariant, +} from './accordion.interfaces'; import { AccordionActionsTemplate, AccordionHeaderTemplate, AccordionIndicatorTemplate } from './accordion.templates'; /** Organizes related content into accessible expandable sections. */ diff --git a/sushi/src/lib/accordion/accordion.templates.ts b/sushi/src/lib/accordion/accordion.templates.ts index 7424817e..b29ac6e2 100644 --- a/sushi/src/lib/accordion/accordion.templates.ts +++ b/sushi/src/lib/accordion/accordion.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { AccordionItemContext } from './accordion.interfaces'; +import { type AccordionItemContext } from './accordion.interfaces'; /** Replaces each accordion heading and exposes its item and expanded state. */ @Directive({ selector: 'ng-template[suiAccordionHeader]' }) diff --git a/sushi/src/lib/accordion/index.ts b/sushi/src/lib/accordion/index.ts index b2ff464c..abc15eae 100644 --- a/sushi/src/lib/accordion/index.ts +++ b/sushi/src/lib/accordion/index.ts @@ -1,4 +1,4 @@ export * from './accordion-item.directive'; export * from './accordion.component'; -export * from './accordion.interfaces'; +export type * from './accordion.interfaces'; export * from './accordion.templates'; diff --git a/sushi/src/lib/accordion/testing/accordion-actions.component.spec.ts b/sushi/src/lib/accordion/testing/accordion-actions.component.spec.ts index aed02c7c..a6126882 100644 --- a/sushi/src/lib/accordion/testing/accordion-actions.component.spec.ts +++ b/sushi/src/lib/accordion/testing/accordion-actions.component.spec.ts @@ -1,10 +1,10 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { press, queryAll, render } from '../../../../testing/test-utils'; +import { press, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { AccordionItem } from '../accordion-item.directive'; import { Accordion } from '../accordion.component'; -import { AccordionValue } from '../accordion.interfaces'; +import { type AccordionValue } from '../accordion.interfaces'; import { AccordionActionsTemplate } from '../accordion.templates'; @Component({ @@ -36,13 +36,13 @@ describe('Accordion sibling actions', (): void => { it('projects item and expansion state outside the trigger and heading', (): void => { const fixture: ComponentFixture = render(ActionsHost); const buttons: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - const action: HTMLButtonElement = buttons[1]; + const action: HTMLButtonElement = requireItem(buttons, 1); expect(action.getAttribute('aria-label')).toBe('Add Bowls'); expect(action.closest('h3')).toBeNull(); expect(action.parentElement?.closest('button')).toBeNull(); expect(action.getAttribute('data-expanded')).toBe('false'); - expect(buttons[3].disabled).toBe(true); + expect(requireItem(buttons, 3).disabled).toBe(true); action.click(); fixture.detectChanges(); @@ -50,7 +50,7 @@ describe('Accordion sibling actions', (): void => { expect(fixture.componentInstance.added()).toBe('bowls'); expect(fixture.componentInstance.value()).toEqual([]); - buttons[0].click(); + requireItem(buttons, 0).click(); fixture.detectChanges(); expect(action.getAttribute('data-expanded')).toBe('true'); @@ -58,7 +58,7 @@ describe('Accordion sibling actions', (): void => { it('keeps native action keys from toggling or navigating the Accordion', (): void => { const fixture: ComponentFixture = render(ActionsHost); - const action: HTMLButtonElement = queryAll(fixture, 'button')[1]; + const action: HTMLButtonElement = requireItem(queryAll(fixture, 'button'), 1); action.focus(); for (const key of ['Enter', ' ', 'ArrowDown', 'ArrowUp', 'Home', 'End']) { diff --git a/sushi/src/lib/accordion/testing/accordion.component.spec.ts b/sushi/src/lib/accordion/testing/accordion.component.spec.ts index be09a7eb..3ead0c33 100644 --- a/sushi/src/lib/accordion/testing/accordion.component.spec.ts +++ b/sushi/src/lib/accordion/testing/accordion.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture, TestBed } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { press, query, queryAll, render } from '../../../../testing/test-utils'; +import { press, query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { AccordionItem } from '../accordion-item.directive'; import { Accordion } from '../accordion.component'; import type { AccordionValue } from '../accordion.interfaces'; @@ -50,8 +50,8 @@ describe('Accordion rendering and model', (): void => { const buttons: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); expect(buttons).toHaveLength(3); - expect(buttons[0].getAttribute('aria-expanded')).toBe('false'); - expect(buttons[2].getAttribute('aria-disabled')).toBe('true'); + expect(requireItem(buttons, 0).getAttribute('aria-expanded')).toBe('false'); + expect(requireItem(buttons, 2).getAttribute('aria-disabled')).toBe('true'); expect(query(fixture, '[data-header="ingredients"]').getAttribute('data-expanded')).toBe('false'); expect(query(fixture, '[data-indicator="method"]').textContent).toBe('Toggle'); }); @@ -59,19 +59,19 @@ describe('Accordion rendering and model', (): void => { it('expands one item at a time in single mode', (): void => { const fixture: ComponentFixture = render(AccordionHost); const buttons: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - buttons[0].click(); + requireItem(buttons, 0).click(); fixture.detectChanges(); expect(fixture.componentInstance.value()).toEqual(['ingredients']); expect(query(fixture, '[data-header="ingredients"]').getAttribute('data-expanded')).toBe('true'); expect((fixture.nativeElement as HTMLElement).textContent).toContain('Broth and noodles'); - buttons[1].click(); + requireItem(buttons, 1).click(); fixture.detectChanges(); expect(fixture.componentInstance.value()).toEqual(['method']); - expect(buttons[0].getAttribute('aria-expanded')).toBe('false'); - expect(buttons[1].getAttribute('aria-expanded')).toBe('true'); + expect(requireItem(buttons, 0).getAttribute('aria-expanded')).toBe('false'); + expect(requireItem(buttons, 1).getAttribute('aria-expanded')).toBe('true'); }); }); @@ -128,16 +128,16 @@ describe('Accordion keyboard and disabled behavior', (): void => { it('moves focus between triggers with Angular Aria keyboard navigation', (): void => { const fixture: ComponentFixture = render(AccordionHost); const buttons: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - buttons[0].focus(); - press(buttons[0], 'ArrowDown'); + requireItem(buttons, 0).focus(); + press(requireItem(buttons, 0), 'ArrowDown'); - expect(document.activeElement).toBe(buttons[1]); + expect(document.activeElement).toBe(requireItem(buttons, 1)); - press(buttons[1], 'Enter'); + press(requireItem(buttons, 1), 'Enter'); fixture.detectChanges(); expect(fixture.componentInstance.value()).toEqual(['method']); - press(buttons[1], ' '); + press(requireItem(buttons, 1), ' '); fixture.detectChanges(); expect(fixture.componentInstance.value()).toEqual([]); }); @@ -145,16 +145,16 @@ describe('Accordion keyboard and disabled behavior', (): void => { it('keeps disabled triggers focusable without expanding them', (): void => { const fixture: ComponentFixture = render(AccordionHost); const buttons: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - buttons[2].focus(); - buttons[2].click(); + requireItem(buttons, 2).focus(); + requireItem(buttons, 2).click(); - expect(document.activeElement).toBe(buttons[2]); + expect(document.activeElement).toBe(requireItem(buttons, 2)); expect(fixture.componentInstance.value()).toEqual([]); }); it('blocks the complete group when disabled', (): void => { const fixture: ComponentFixture = render(AccordionHost); - const first: HTMLButtonElement = queryAll(fixture, 'button')[0]; + const first: HTMLButtonElement = requireItem(queryAll(fixture, 'button'), 0); fixture.componentInstance.disabled.set(true); fixture.detectChanges(); first.click(); diff --git a/sushi/src/lib/auto-focus/auto-focus.directive.ts b/sushi/src/lib/auto-focus/auto-focus.directive.ts index 1cf7767d..aff3c821 100644 --- a/sushi/src/lib/auto-focus/auto-focus.directive.ts +++ b/sushi/src/lib/auto-focus/auto-focus.directive.ts @@ -1,4 +1,12 @@ -import { afterNextRender, booleanAttribute, Directive, ElementRef, inject, input, InputSignalWithTransform } from '@angular/core'; +import { + afterNextRender, + booleanAttribute, + Directive, + ElementRef, + inject, + input, + type InputSignalWithTransform, +} from '@angular/core'; @Directive({ selector: '[suiAutoFocus]', diff --git a/sushi/src/lib/auto-focus/testing/auto-focus.directive.spec.ts b/sushi/src/lib/auto-focus/testing/auto-focus.directive.spec.ts index adb2f4da..29d814bc 100644 --- a/sushi/src/lib/auto-focus/testing/auto-focus.directive.spec.ts +++ b/sushi/src/lib/auto-focus/testing/auto-focus.directive.spec.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { AutoFocus } from '../auto-focus.directive'; diff --git a/sushi/src/lib/autocomplete/autocomplete.component.ts b/sushi/src/lib/autocomplete/autocomplete.component.ts index 27cdfce4..27e415ef 100644 --- a/sushi/src/lib/autocomplete/autocomplete.component.ts +++ b/sushi/src/lib/autocomplete/autocomplete.component.ts @@ -10,33 +10,33 @@ import { DestroyRef, inject, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, numberAttribute, output, - OutputEmitterRef, + type OutputEmitterRef, signal, - Signal, + type Signal, TemplateRef, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; +import { type FormValueControl } from '@angular/forms/signals'; import { Combobox, ComboboxPopup, ComboboxWidget } from '@angular/aria/combobox'; import { Listbox, Option } from '@angular/aria/listbox'; -import { CdkConnectedOverlay, CdkOverlayOrigin, ConnectedPosition } from '@angular/cdk/overlay'; -import { FormControlSeverity, FormControlSize, FormControlState } from '../form-control'; +import { CdkConnectedOverlay, CdkOverlayOrigin, type ConnectedPosition } from '@angular/cdk/overlay'; +import { type FormControlSeverity, type FormControlSize, FormControlState } from '../form-control'; import { compareSelectionValues, filterSelectionOption, SELECTION_ABOVE, SELECTION_BELOW } from '../selection'; import { Spinner } from '../spinner'; import { - AutocompleteCompareWith, - AutocompleteFilter, - AutocompleteItemContext, - AutocompleteOption, - AutocompleteStatusContext, - AutocompleteValue, + type AutocompleteCompareWith, + type AutocompleteFilter, + type AutocompleteItemContext, + type AutocompleteOption, + type AutocompleteStatusContext, + type AutocompleteValue, } from './autocomplete.interfaces'; import { AutocompleteEmptyTemplate, @@ -212,6 +212,7 @@ export class Autocomplete if (!this.expanded()) return; const listbox: Listbox | undefined = this.listbox(); if (!listbox?.activeDescendant()) return; + listbox.scrollActiveItemIntoView(); }, }); @@ -236,6 +237,7 @@ export class Autocomplete /** Focuses the input and opens all suggestions without clearing the displayed selection or applying minQueryLength/delay. Emits an empty query for remote loaders. Disabled or loading controls do not open. */ public show(): void { if (this.disabled() || this.loading() || this.expanded()) return; + clearTimeout(this.queryTimer); this.beginEdit(); this.showAll.set(true); @@ -288,6 +290,7 @@ export class Autocomplete clearTimeout(this.queryTimer); const value: AutocompleteValue = values.at(0) ?? null; if (value === null) return; + this.value.set(value); this.query.set(null); this.editSession = null; @@ -308,6 +311,7 @@ export class Autocomplete } this.expanded.set(expanded); if (expanded) return; + clearTimeout(this.queryTimer); this.finishEdit(); this.touch.emit(); @@ -365,6 +369,7 @@ export class Autocomplete this.queryChange.emit(query); this.expanded.set(true); }; + const delay: number = Math.max(0, this.delay()); if (delay === 0) complete(); else this.queryTimer = setTimeout(complete, delay); diff --git a/sushi/src/lib/autocomplete/autocomplete.interfaces.ts b/sushi/src/lib/autocomplete/autocomplete.interfaces.ts index 999ce70f..5c780f92 100644 --- a/sushi/src/lib/autocomplete/autocomplete.interfaces.ts +++ b/sushi/src/lib/autocomplete/autocomplete.interfaces.ts @@ -1,5 +1,5 @@ -import { SelectionCompareWith, SelectionFilter, SelectionOption } from '../selection'; -import { SelectionValue } from '../sushi.types'; +import { type SelectionCompareWith, type SelectionFilter, type SelectionOption } from '../selection'; +import { type SelectionValue } from '../sushi.types'; /** Value held by an autocomplete control. */ export type AutocompleteValue = SelectionValue | null; diff --git a/sushi/src/lib/autocomplete/autocomplete.templates.ts b/sushi/src/lib/autocomplete/autocomplete.templates.ts index 2dc34559..dc4b2214 100644 --- a/sushi/src/lib/autocomplete/autocomplete.templates.ts +++ b/sushi/src/lib/autocomplete/autocomplete.templates.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { AutocompleteItemContext, AutocompleteOption, AutocompleteStatusContext } from './autocomplete.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type AutocompleteItemContext, type AutocompleteOption, type AutocompleteStatusContext } from './autocomplete.interfaces'; /** Replaces each suggestion and exposes its option, index, and selection state. */ @Directive({ selector: 'ng-template[suiAutocompleteItem]' }) diff --git a/sushi/src/lib/autocomplete/index.ts b/sushi/src/lib/autocomplete/index.ts index 0d55949c..23f16090 100644 --- a/sushi/src/lib/autocomplete/index.ts +++ b/sushi/src/lib/autocomplete/index.ts @@ -1,3 +1,3 @@ export * from './autocomplete.component'; -export * from './autocomplete.interfaces'; +export type * from './autocomplete.interfaces'; export * from './autocomplete.templates'; diff --git a/sushi/src/lib/autocomplete/testing/autocomplete.component.spec.ts b/sushi/src/lib/autocomplete/testing/autocomplete.component.spec.ts index 5fbe0aa0..29051ad6 100644 --- a/sushi/src/lib/autocomplete/testing/autocomplete.component.spec.ts +++ b/sushi/src/lib/autocomplete/testing/autocomplete.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { press, query, render } from '../../../../testing/test-utils'; import { Autocomplete } from '../autocomplete.component'; diff --git a/sushi/src/lib/avatar/avatar.component.ts b/sushi/src/lib/avatar/avatar.component.ts index cd9e2e39..80602bf4 100644 --- a/sushi/src/lib/avatar/avatar.component.ts +++ b/sushi/src/lib/avatar/avatar.component.ts @@ -4,12 +4,12 @@ import { Component, computed, input, - InputSignal, - InputSignalWithTransform, - Signal, + type InputSignal, + type InputSignalWithTransform, + type Signal, } from '@angular/core'; -import { AvatarMaskHalf, AvatarShape, AvatarSize, AvatarStatus } from './avatar.interfaces'; -import { Mask, MaskShape } from '../mask'; +import { type AvatarMaskHalf, type AvatarShape, type AvatarSize, type AvatarStatus } from './avatar.interfaces'; +import { Mask, type MaskShape } from '../mask'; @Component({ selector: 'sui-avatar', diff --git a/sushi/src/lib/avatar/avatar.interfaces.ts b/sushi/src/lib/avatar/avatar.interfaces.ts index 290dbe9e..f55812d3 100644 --- a/sushi/src/lib/avatar/avatar.interfaces.ts +++ b/sushi/src/lib/avatar/avatar.interfaces.ts @@ -1,5 +1,5 @@ -import { ComponentSize } from '../sushi.types'; -import { MaskHalf, MaskShape } from '../mask'; +import { type ComponentSize } from '../sushi.types'; +import { type MaskHalf, type MaskShape } from '../mask'; /** Presence states displayed by an avatar. */ export type AvatarStatus = 'online' | 'offline'; diff --git a/sushi/src/lib/avatar/index.ts b/sushi/src/lib/avatar/index.ts index 259bdb09..ac33667c 100644 --- a/sushi/src/lib/avatar/index.ts +++ b/sushi/src/lib/avatar/index.ts @@ -1,3 +1,3 @@ export * from './avatar-group.directive'; export * from './avatar.component'; -export * from './avatar.interfaces'; +export type * from './avatar.interfaces'; diff --git a/sushi/src/lib/avatar/testing/avatar.component.spec.ts b/sushi/src/lib/avatar/testing/avatar.component.spec.ts index 52b5e986..d9e36a02 100644 --- a/sushi/src/lib/avatar/testing/avatar.component.spec.ts +++ b/sushi/src/lib/avatar/testing/avatar.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { AvatarGroup } from '../avatar-group.directive'; diff --git a/sushi/src/lib/badge/badge.directive.ts b/sushi/src/lib/badge/badge.directive.ts index 787dd970..81ed526d 100644 --- a/sushi/src/lib/badge/badge.directive.ts +++ b/sushi/src/lib/badge/badge.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { BadgeSeverity, BadgeSize, BadgeVariant } from './badge.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type BadgeSeverity, type BadgeSize, type BadgeVariant } from './badge.interfaces'; /** Styles compact, non-interactive labels for status or metadata. */ @Directive({ diff --git a/sushi/src/lib/badge/badge.interfaces.ts b/sushi/src/lib/badge/badge.interfaces.ts index 6b85063c..d646e8d4 100644 --- a/sushi/src/lib/badge/badge.interfaces.ts +++ b/sushi/src/lib/badge/badge.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize, ThemeSeverity } from '../sushi.types'; +import { type ComponentSize, type ThemeSeverity } from '../sushi.types'; /** Semantic colors available to badges. */ export type BadgeSeverity = ThemeSeverity; diff --git a/sushi/src/lib/badge/index.ts b/sushi/src/lib/badge/index.ts index 8c0ae71a..d78c0caa 100644 --- a/sushi/src/lib/badge/index.ts +++ b/sushi/src/lib/badge/index.ts @@ -1,2 +1,2 @@ export * from './badge.directive'; -export * from './badge.interfaces'; +export type * from './badge.interfaces'; diff --git a/sushi/src/lib/badge/testing/badge.directive.spec.ts b/sushi/src/lib/badge/testing/badge.directive.spec.ts index 3eae5fce..2f8a0e66 100644 --- a/sushi/src/lib/badge/testing/badge.directive.spec.ts +++ b/sushi/src/lib/badge/testing/badge.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Badge } from '../badge.directive'; diff --git a/sushi/src/lib/breadcrumb/breadcrumb.component.css b/sushi/src/lib/breadcrumb/breadcrumb.component.css index 411e0aff..169bf879 100644 --- a/sushi/src/lib/breadcrumb/breadcrumb.component.css +++ b/sushi/src/lib/breadcrumb/breadcrumb.component.css @@ -19,7 +19,7 @@ a:focus-visible { outline: var(--sui-focus-ring-width) solid var(--sui-focus-ring-color, currentColor); outline-offset: var(--sui-focus-ring-offset); } -[aria-current='page'] { +[aria-current="page"] { color: var(--sui-breadcrumb-current-color); cursor: default; font-weight: 600; @@ -42,7 +42,7 @@ li { } a, -[aria-current='page'] { +[aria-current="page"] { min-width: 0; overflow-wrap: anywhere; } diff --git a/sushi/src/lib/breadcrumb/breadcrumb.component.ts b/sushi/src/lib/breadcrumb/breadcrumb.component.ts index a51eb98e..de83ebe2 100644 --- a/sushi/src/lib/breadcrumb/breadcrumb.component.ts +++ b/sushi/src/lib/breadcrumb/breadcrumb.component.ts @@ -1,7 +1,15 @@ import { NgTemplateOutlet } from '@angular/common'; -import { ChangeDetectionStrategy, Component, contentChild, input, InputSignal, Signal, TemplateRef } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + contentChild, + input, + type InputSignal, + type Signal, + TemplateRef, +} from '@angular/core'; import { RouterLink } from '@angular/router'; -import { BreadcrumbItem, BreadcrumbItemContext } from './breadcrumb.interfaces'; +import { type BreadcrumbItem, type BreadcrumbItemContext } from './breadcrumb.interfaces'; import { BreadcrumbItemTemplate } from './breadcrumb.templates'; /** Renders the hierarchical path to the current page as native navigation links. */ diff --git a/sushi/src/lib/breadcrumb/breadcrumb.templates.ts b/sushi/src/lib/breadcrumb/breadcrumb.templates.ts index 4a564a7d..18266dfb 100644 --- a/sushi/src/lib/breadcrumb/breadcrumb.templates.ts +++ b/sushi/src/lib/breadcrumb/breadcrumb.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { BreadcrumbItemContext } from './breadcrumb.interfaces'; +import { type BreadcrumbItemContext } from './breadcrumb.interfaces'; /** Replaces each Breadcrumb label while preserving navigation semantics. */ @Directive({ selector: 'ng-template[suiBreadcrumbItem]' }) diff --git a/sushi/src/lib/breadcrumb/index.ts b/sushi/src/lib/breadcrumb/index.ts index 83975152..f99b2e8a 100644 --- a/sushi/src/lib/breadcrumb/index.ts +++ b/sushi/src/lib/breadcrumb/index.ts @@ -1,3 +1,3 @@ export * from './breadcrumb.component'; -export * from './breadcrumb.interfaces'; +export type * from './breadcrumb.interfaces'; export * from './breadcrumb.templates'; diff --git a/sushi/src/lib/breadcrumb/testing/breadcrumb.component.spec.ts b/sushi/src/lib/breadcrumb/testing/breadcrumb.component.spec.ts index c1e3b460..5d9f2b73 100644 --- a/sushi/src/lib/breadcrumb/testing/breadcrumb.component.spec.ts +++ b/sushi/src/lib/breadcrumb/testing/breadcrumb.component.spec.ts @@ -1,10 +1,10 @@ import { Component } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { type ComponentFixture, TestBed } from '@angular/core/testing'; import { provideRouter } from '@angular/router'; import { beforeEach, describe, expect, it } from 'vitest'; import { query, queryAll, render } from '../../../../testing/test-utils'; import { Breadcrumb } from '../breadcrumb.component'; -import { BreadcrumbItem } from '../breadcrumb.interfaces'; +import { type BreadcrumbItem } from '../breadcrumb.interfaces'; import { BreadcrumbItemTemplate } from '../breadcrumb.templates'; @Component({ diff --git a/sushi/src/lib/button/button.directive.ts b/sushi/src/lib/button/button.directive.ts index 74466c9a..2653c03f 100644 --- a/sushi/src/lib/button/button.directive.ts +++ b/sushi/src/lib/button/button.directive.ts @@ -6,13 +6,13 @@ import { ElementRef, inject, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, numberAttribute, Renderer2, - Signal, + type Signal, } from '@angular/core'; -import { ButtonSeverity, ButtonShape, ButtonSize, ButtonVariant } from './button.interfaces'; +import { type ButtonSeverity, type ButtonShape, type ButtonSize, type ButtonVariant } from './button.interfaces'; @Directive({ selector: 'button[suiButton], a[suiButton]', diff --git a/sushi/src/lib/button/button.interfaces.ts b/sushi/src/lib/button/button.interfaces.ts index c65ac6f8..3e5eb967 100644 --- a/sushi/src/lib/button/button.interfaces.ts +++ b/sushi/src/lib/button/button.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize, ThemeSeverity } from '../sushi.types'; +import { type ComponentSize, type ThemeSeverity } from '../sushi.types'; /** Semantic colors available to buttons. */ export type ButtonSeverity = ThemeSeverity; diff --git a/sushi/src/lib/button/index.ts b/sushi/src/lib/button/index.ts index 419b61a8..a2ed56a1 100644 --- a/sushi/src/lib/button/index.ts +++ b/sushi/src/lib/button/index.ts @@ -1,2 +1,2 @@ export * from './button.directive'; -export * from './button.interfaces'; +export type * from './button.interfaces'; diff --git a/sushi/src/lib/button/testing/button.directive.spec.ts b/sushi/src/lib/button/testing/button.directive.spec.ts index bb6df71b..51721ee5 100644 --- a/sushi/src/lib/button/testing/button.directive.spec.ts +++ b/sushi/src/lib/button/testing/button.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Button } from '../button.directive'; diff --git a/sushi/src/lib/card/card.component.ts b/sushi/src/lib/card/card.component.ts index a551652e..748ef673 100644 --- a/sushi/src/lib/card/card.component.ts +++ b/sushi/src/lib/card/card.component.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, input, InputSignal, ViewEncapsulation } from '@angular/core'; -import { CardSize, CardVariant } from './card.interfaces'; +import { ChangeDetectionStrategy, Component, input, type InputSignal, ViewEncapsulation } from '@angular/core'; +import { type CardSize, type CardVariant } from './card.interfaces'; @Component({ selector: 'sui-card', diff --git a/sushi/src/lib/card/card.interfaces.ts b/sushi/src/lib/card/card.interfaces.ts index 946bb352..7a7b8f67 100644 --- a/sushi/src/lib/card/card.interfaces.ts +++ b/sushi/src/lib/card/card.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize } from '../sushi.types'; +import { type ComponentSize } from '../sushi.types'; /** Surface treatments available to cards. */ export type CardVariant = 'plain' | 'border' | 'dash'; diff --git a/sushi/src/lib/card/index.ts b/sushi/src/lib/card/index.ts index bc3b5d82..f4d9b9b0 100644 --- a/sushi/src/lib/card/index.ts +++ b/sushi/src/lib/card/index.ts @@ -1,5 +1,5 @@ export * from './card-actions.directive'; export * from './card.component'; -export * from './card.interfaces'; +export type * from './card.interfaces'; export * from './card-media.directive'; export * from './card-title.directive'; diff --git a/sushi/src/lib/card/testing/card.component.spec.ts b/sushi/src/lib/card/testing/card.component.spec.ts index 60e75349..b0b31da7 100644 --- a/sushi/src/lib/card/testing/card.component.spec.ts +++ b/sushi/src/lib/card/testing/card.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { CardActions } from '../card-actions.directive'; diff --git a/sushi/src/lib/checkbox/checkbox.directive.ts b/sushi/src/lib/checkbox/checkbox.directive.ts index 617c9f7a..e920bede 100644 --- a/sushi/src/lib/checkbox/checkbox.directive.ts +++ b/sushi/src/lib/checkbox/checkbox.directive.ts @@ -1,4 +1,4 @@ -import { booleanAttribute, Directive, input, InputSignalWithTransform } from '@angular/core'; +import { booleanAttribute, Directive, input, type InputSignalWithTransform } from '@angular/core'; import { SelectionControlState } from '../form-control'; /** Styles a native checkbox and exposes consistent form state. */ diff --git a/sushi/src/lib/checkbox/testing/checkbox.directive.spec.ts b/sushi/src/lib/checkbox/testing/checkbox.directive.spec.ts index c435bb4f..9ddffc14 100644 --- a/sushi/src/lib/checkbox/testing/checkbox.directive.spec.ts +++ b/sushi/src/lib/checkbox/testing/checkbox.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import type { FormControlSeverity, FormControlSize } from '../../form-control'; diff --git a/sushi/src/lib/chip/chip.component.css b/sushi/src/lib/chip/chip.component.css index a833c7fe..81b06558 100644 --- a/sushi/src/lib/chip/chip.component.css +++ b/sushi/src/lib/chip/chip.component.css @@ -13,7 +13,7 @@ @apply inline-flex h-8 max-w-full items-center gap-2 px-3 text-sm font-medium; } -:host(:not([class*='badge-'])) { +:host(:not([class*="badge-"])) { background-color: var(--sui-chip-background); color: var(--sui-chip-color); } diff --git a/sushi/src/lib/chip/chip.component.ts b/sushi/src/lib/chip/chip.component.ts index 51d7d3c2..d7f5b8c9 100644 --- a/sushi/src/lib/chip/chip.component.ts +++ b/sushi/src/lib/chip/chip.component.ts @@ -6,17 +6,17 @@ import { computed, contentChild, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, output, - OutputEmitterRef, + type OutputEmitterRef, signal, - Signal, + type Signal, TemplateRef, - WritableSignal, + type WritableSignal, } from '@angular/core'; import { LucideX } from '@lucide/angular'; -import { ChipContentContext, ChipSeverity, ChipSize, ChipVariant } from './chip.interfaces'; +import { type ChipContentContext, type ChipSeverity, type ChipSize, type ChipVariant } from './chip.interfaces'; import { ChipContentTemplate, ChipRemoveIconTemplate } from './chip.templates'; /** Displays a compact value that can optionally be removed. */ diff --git a/sushi/src/lib/chip/chip.interfaces.ts b/sushi/src/lib/chip/chip.interfaces.ts index d7191dcd..8f2bc19e 100644 --- a/sushi/src/lib/chip/chip.interfaces.ts +++ b/sushi/src/lib/chip/chip.interfaces.ts @@ -1,5 +1,5 @@ -import { ComponentSize } from '../sushi.types'; -import { BadgeSeverity, BadgeVariant } from '../badge'; +import { type ComponentSize } from '../sushi.types'; +import { type BadgeSeverity, type BadgeVariant } from '../badge'; /** Size scale available to chips. */ export type ChipSize = ComponentSize; diff --git a/sushi/src/lib/chip/chip.templates.ts b/sushi/src/lib/chip/chip.templates.ts index c8d1e088..688e07d4 100644 --- a/sushi/src/lib/chip/chip.templates.ts +++ b/sushi/src/lib/chip/chip.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { ChipContentContext } from './chip.interfaces'; +import { type ChipContentContext } from './chip.interfaces'; /** Replaces the projected content and label of a chip. */ @Directive({ selector: 'ng-template[suiChipContent]' }) diff --git a/sushi/src/lib/chip/index.ts b/sushi/src/lib/chip/index.ts index e0b6e775..5e3d3146 100644 --- a/sushi/src/lib/chip/index.ts +++ b/sushi/src/lib/chip/index.ts @@ -1,3 +1,3 @@ export * from './chip.component'; -export * from './chip.interfaces'; +export type * from './chip.interfaces'; export * from './chip.templates'; diff --git a/sushi/src/lib/chip/testing/chip.component.spec.ts b/sushi/src/lib/chip/testing/chip.component.spec.ts index c8f25d0b..a6e249fc 100644 --- a/sushi/src/lib/chip/testing/chip.component.spec.ts +++ b/sushi/src/lib/chip/testing/chip.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { press, query, render } from '../../../../testing/test-utils'; import { Chip } from '../chip.component'; diff --git a/sushi/src/lib/code/code-line.directive.ts b/sushi/src/lib/code/code-line.directive.ts index 1716925e..bd73676c 100644 --- a/sushi/src/lib/code/code-line.directive.ts +++ b/sushi/src/lib/code/code-line.directive.ts @@ -1,4 +1,4 @@ -import { Directive, ElementRef, inject, input, InputSignal } from '@angular/core'; +import { Directive, ElementRef, inject, input, type InputSignal } from '@angular/core'; @Directive({ selector: '[suiCodeLine]', diff --git a/sushi/src/lib/code/code.component.ts b/sushi/src/lib/code/code.component.ts index ee9bb579..e046b7b5 100644 --- a/sushi/src/lib/code/code.component.ts +++ b/sushi/src/lib/code/code.component.ts @@ -9,17 +9,17 @@ import { DestroyRef, inject, input, - InputSignal, - InputSignalWithTransform, - Signal, + type InputSignal, + type InputSignalWithTransform, + type Signal, signal, TemplateRef, - WritableSignal, + type WritableSignal, } from '@angular/core'; import { LucideCheck, LucideCopy } from '@lucide/angular'; -import { Button, ButtonShape } from '../button'; +import { Button, type ButtonShape } from '../button'; import { nonNegativeInteger } from '../number.transforms'; -import { CodeButtonContext } from './code.interfaces'; +import { type CodeButtonContext } from './code.interfaces'; import { CodeLine } from './code-line.directive'; import { CodeButtonOffTemplate, CodeButtonOnTemplate, CodeButtonTemplate } from './code.templates'; diff --git a/sushi/src/lib/code/code.templates.ts b/sushi/src/lib/code/code.templates.ts index a1eb1aef..e09b4f1d 100644 --- a/sushi/src/lib/code/code.templates.ts +++ b/sushi/src/lib/code/code.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { CodeButtonContext } from './code.interfaces'; +import { type CodeButtonContext } from './code.interfaces'; /** Replaces the complete copy control and exposes copy state and behavior. */ @Directive({ diff --git a/sushi/src/lib/code/index.ts b/sushi/src/lib/code/index.ts index 40170b13..2e1e948a 100644 --- a/sushi/src/lib/code/index.ts +++ b/sushi/src/lib/code/index.ts @@ -1,4 +1,4 @@ export * from './code.component'; export * from './code-line.directive'; -export * from './code.interfaces'; +export type * from './code.interfaces'; export * from './code.templates'; diff --git a/sushi/src/lib/code/testing/code.component.spec.ts b/sushi/src/lib/code/testing/code.component.spec.ts index 7f03e8cb..a45c7579 100644 --- a/sushi/src/lib/code/testing/code.component.spec.ts +++ b/sushi/src/lib/code/testing/code.component.spec.ts @@ -1,7 +1,7 @@ import { Clipboard } from '@angular/cdk/clipboard'; import { Component } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; +import { type ComponentFixture, TestBed } from '@angular/core/testing'; +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { CodeLine } from '../code-line.directive'; import { Code } from '../code.component'; diff --git a/sushi/src/lib/color-picker/color-picker.component.css b/sushi/src/lib/color-picker/color-picker.component.css index 66d6e3cc..7c9ef067 100644 --- a/sushi/src/lib/color-picker/color-picker.component.css +++ b/sushi/src/lib/color-picker/color-picker.component.css @@ -77,25 +77,25 @@ 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; } -.sui-color-picker__preset:not([aria-disabled='true']):hover { +.sui-color-picker__preset:not([aria-disabled="true"]):hover { border-color: color-mix(in oklab, var(--color-base-content) 35%, transparent); background-color: var(--sui-color-picker-preset-background); color: var(--color-base-content); } -.sui-color-picker__preset:not([aria-disabled='true'])[aria-checked='true'] { +.sui-color-picker__preset:not([aria-disabled="true"])[aria-checked="true"] { border-color: var(--sui-color-picker-preset-ring); background-color: var(--sui-color-picker-preset-background); color: var(--sui-color-picker-preset-content); } -.sui-color-picker__preset:not([aria-disabled='true']):focus-visible { +.sui-color-picker__preset:not([aria-disabled="true"]):focus-visible { border-color: var(--sui-color-picker-preset-ring); outline: var(--sui-focus-ring-width) solid var(--sui-focus-ring-color, var(--sui-color-picker-preset-ring)); outline-offset: var(--sui-focus-ring-offset); } -.sui-color-picker__preset[aria-disabled='true'] { +.sui-color-picker__preset[aria-disabled="true"] { --btn-color: var(--color-base-200); --btn-fg: color-mix(in oklab, var(--color-base-content) 45%, transparent); --btn-rest-fg: color-mix(in oklab, var(--color-base-content) 45%, transparent); @@ -110,7 +110,7 @@ user-select: none; } -.sui-color-picker__preset[aria-disabled='true'] .sui-color-picker__preset-swatch { +.sui-color-picker__preset[aria-disabled="true"] .sui-color-picker__preset-swatch { opacity: 0.4; } diff --git a/sushi/src/lib/color-picker/color-picker.component.ts b/sushi/src/lib/color-picker/color-picker.component.ts index 2cb6f6ee..a935c774 100644 --- a/sushi/src/lib/color-picker/color-picker.component.ts +++ b/sushi/src/lib/color-picker/color-picker.component.ts @@ -8,23 +8,23 @@ import { ElementRef, inject, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, signal, TemplateRef, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; +import { type FormValueControl } from '@angular/forms/signals'; import { Button } from '../button'; import { Input } from '../input'; import { Join, JoinItem } from '../join'; -import { ColorPickerPresetContext, ColorPickerPresetValue } from './color-picker.interfaces'; +import { type ColorPickerPresetContext, type ColorPickerPresetValue } from './color-picker.interfaces'; import { ColorPickerPresetTemplate } from './color-picker.templates'; interface ColorPickerOption { @@ -144,7 +144,9 @@ export class ColorPicker implements FormValueControl { protected handleColorInput(event: Event): void { if (this.disabled()) return; - const input: HTMLInputElement = event.currentTarget as HTMLInputElement; + const input: EventTarget | null = event.currentTarget; + if (!(input instanceof HTMLInputElement)) return; + this.draft.set(null); this.localInvalid.set(false); this.customColor.set(input.value); @@ -153,7 +155,9 @@ export class ColorPicker implements FormValueControl { protected handleTextInput(event: Event): void { if (this.disabled()) return; - const input: HTMLInputElement = event.currentTarget as HTMLInputElement; + const input: EventTarget | null = event.currentTarget; + if (!(input instanceof HTMLInputElement)) return; + this.draft.set(input.value); if (!HEX_COLOR_PATTERN.test(input.value)) return; this.localInvalid.set(false); @@ -174,6 +178,7 @@ export class ColorPicker implements FormValueControl { if (this.disabled() || option.disabled) return; const current: string = this.value(); if (!this.isPreset(current)) this.customColor.set(current); + this.draft.set(null); this.localInvalid.set(false); this.value.set(option.color); @@ -182,6 +187,7 @@ export class ColorPicker implements FormValueControl { protected isTabStop(option: ColorPickerOption, index: number): boolean { if (this.disabled() || option.disabled) return false; if (option.selected) return true; + const options: readonly ColorPickerOption[] = this.options(); const hasEnabledSelection: boolean = options.some( (candidate: ColorPickerOption): boolean => candidate.selected && !candidate.disabled, @@ -204,16 +210,19 @@ export class ColorPicker implements FormValueControl { const currentPosition: number = Math.max(0, enabledIndices.indexOf(index)); const direction: number = ['ArrowRight', 'ArrowDown'].includes(event.key) ? 1 : -1; - const nextPosition: number = - event.key === 'Home' - ? 0 - : event.key === 'End' - ? enabledIndices.length - 1 - : (currentPosition + direction + enabledIndices.length) % enabledIndices.length; - const nextIndex: number = enabledIndices[nextPosition]; + let nextPosition: number = (currentPosition + direction + enabledIndices.length) % enabledIndices.length; + if (event.key === 'Home') nextPosition = 0; + else if (event.key === 'End') nextPosition = enabledIndices.length - 1; + + const nextIndex: number | undefined = enabledIndices[nextPosition]; + if (nextIndex === undefined) return; + + const option: ColorPickerOption | undefined = options[nextIndex]; + if (!option) return; + const buttons: NodeListOf = this.element.nativeElement.querySelectorAll('[role="radio"]'); buttons.item(nextIndex).focus(); - this.selectOption(options[nextIndex]); + this.selectOption(option); } protected presetContext(option: ColorPickerOption): ColorPickerPresetContext { @@ -228,7 +237,8 @@ export class ColorPicker implements FormValueControl { } protected handleFocusout(event: FocusEvent): void { - if (!this.element.nativeElement.contains(event.relatedTarget as Node | null)) this.touch.emit(); + const destination: EventTarget | null = event.relatedTarget; + if (!(destination instanceof Node) || !this.element.nativeElement.contains(destination)) this.touch.emit(); } protected handleDisabledEvent(event: Event): void { diff --git a/sushi/src/lib/color-picker/color-picker.templates.ts b/sushi/src/lib/color-picker/color-picker.templates.ts index 79b85d7d..1f6d4c56 100644 --- a/sushi/src/lib/color-picker/color-picker.templates.ts +++ b/sushi/src/lib/color-picker/color-picker.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { ColorPickerPresetContext } from './color-picker.interfaces'; +import { type ColorPickerPresetContext } from './color-picker.interfaces'; /** Replaces each preset action's default swatch while preserving selection, keyboard behavior, and themed interaction states. */ @Directive({ selector: 'ng-template[suiColorPickerPreset]' }) diff --git a/sushi/src/lib/color-picker/index.ts b/sushi/src/lib/color-picker/index.ts index dd89e281..e0d60e76 100644 --- a/sushi/src/lib/color-picker/index.ts +++ b/sushi/src/lib/color-picker/index.ts @@ -1,3 +1,3 @@ export * from './color-picker.component'; -export * from './color-picker.interfaces'; +export type * from './color-picker.interfaces'; export * from './color-picker.templates'; diff --git a/sushi/src/lib/color-picker/testing/color-picker.component.spec.ts b/sushi/src/lib/color-picker/testing/color-picker.component.spec.ts index 3c61eca4..35206eae 100644 --- a/sushi/src/lib/color-picker/testing/color-picker.component.spec.ts +++ b/sushi/src/lib/color-picker/testing/color-picker.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { press, query, queryAll, render } from '../../../../testing/test-utils'; +import { press, query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { ColorPicker } from '../color-picker.component'; import type { ColorPickerPresetValue } from '../color-picker.interfaces'; import { ColorPickerPresetTemplate } from '../color-picker.templates'; @@ -121,13 +121,13 @@ describe('ColorPicker presets', (): void => { it('selects enabled presets and skips disabled presets with arrow keys', (): void => { const fixture: ComponentFixture = render(ColorPickerHost); const radios: readonly HTMLButtonElement[] = queryAll(fixture, 'button[role="radio"]') as readonly HTMLButtonElement[]; - press(radios[0], 'ArrowRight'); + press(requireItem(radios, 0), 'ArrowRight'); fixture.detectChanges(); - expect(document.activeElement).toBe(radios[2]); + expect(document.activeElement).toBe(requireItem(radios, 2)); expect(fixture.componentInstance.value()).toBe('#0000ff'); - radios[1].click(); + requireItem(radios, 1).click(); expect(fixture.componentInstance.value()).toBe('#0000ff'); }); @@ -140,7 +140,7 @@ describe('ColorPicker presets', (): void => { fixture.detectChanges(); const radios: readonly HTMLButtonElement[] = queryAll(fixture, 'button[role="radio"]') as readonly HTMLButtonElement[]; - radios[0].click(); + requireItem(radios, 0).click(); fixture.detectChanges(); const custom: HTMLButtonElement | undefined = radios.at(-1); if (!custom) throw new Error('Expected the custom-color preset.'); diff --git a/sushi/src/lib/data-view/data-view.component.ts b/sushi/src/lib/data-view/data-view.component.ts index 78b426fa..ee9f6fdc 100644 --- a/sushi/src/lib/data-view/data-view.component.ts +++ b/sushi/src/lib/data-view/data-view.component.ts @@ -6,15 +6,15 @@ import { Component, computed, contentChild, - ElementRef, + type ElementRef, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, TemplateRef, viewChild, } from '@angular/core'; @@ -22,15 +22,15 @@ import { LucideGrid2x2, LucideList } from '@lucide/angular'; import { Button } from '../button'; import { nonNegativeInteger, positiveInteger } from '../number.transforms'; import { Pagination } from '../pagination'; -import { SelectButton, SelectButtonOption, SelectButtonOptionTemplate, SelectButtonValue } from '../select-button'; +import { SelectButton, type SelectButtonOption, SelectButtonOptionTemplate, type SelectButtonValue } from '../select-button'; import { Spinner } from '../spinner'; import { - DataViewCollectionContext, - DataViewItemContext, - DataViewLayout, - DataViewLoadMoreContext, - DataViewPaginatorPosition, - DataViewTrackBy, + type DataViewCollectionContext, + type DataViewItemContext, + type DataViewLayout, + type DataViewLoadMoreContext, + type DataViewPaginatorPosition, + type DataViewTrackBy, } from './data-view.interfaces'; import { DataViewEmptyTemplate, diff --git a/sushi/src/lib/data-view/data-view.templates.ts b/sushi/src/lib/data-view/data-view.templates.ts index ebf94052..8d6d6b6b 100644 --- a/sushi/src/lib/data-view/data-view.templates.ts +++ b/sushi/src/lib/data-view/data-view.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { DataViewCollectionContext, DataViewItemContext, DataViewLoadMoreContext } from './data-view.interfaces'; +import { type DataViewCollectionContext, type DataViewItemContext, type DataViewLoadMoreContext } from './data-view.interfaces'; /** Renders every item and exposes its layout and page position. */ @Directive({ selector: 'ng-template[suiDataViewItem]' }) diff --git a/sushi/src/lib/data-view/index.ts b/sushi/src/lib/data-view/index.ts index 0f4d638a..87d63eaf 100644 --- a/sushi/src/lib/data-view/index.ts +++ b/sushi/src/lib/data-view/index.ts @@ -1,3 +1,3 @@ export * from './data-view.component'; -export * from './data-view.interfaces'; +export type * from './data-view.interfaces'; export * from './data-view.templates'; diff --git a/sushi/src/lib/data-view/testing/data-view.component.spec.ts b/sushi/src/lib/data-view/testing/data-view.component.spec.ts index ba217f34..3e37c5a3 100644 --- a/sushi/src/lib/data-view/testing/data-view.component.spec.ts +++ b/sushi/src/lib/data-view/testing/data-view.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { query, queryAll, render } from '../../../../testing/test-utils'; +import { query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { DataView } from '../data-view.component'; import type { DataViewLayout } from '../data-view.interfaces'; import { @@ -85,7 +85,7 @@ describe('DataView collection rendering', (): void => { it('switches layouts through its SUSHI SelectButton', (): void => { const fixture: ComponentFixture = render(DataViewHost); const radios: readonly HTMLButtonElement[] = queryAll(fixture, '[role="radio"]') as readonly HTMLButtonElement[]; - radios[1].click(); + requireItem(radios, 1).click(); fixture.detectChanges(); expect(fixture.componentInstance.layout()).toBe('grid'); diff --git a/sushi/src/lib/dialog/dialog-close.directive.ts b/sushi/src/lib/dialog/dialog-close.directive.ts index f43ebeb0..4461ae20 100644 --- a/sushi/src/lib/dialog/dialog-close.directive.ts +++ b/sushi/src/lib/dialog/dialog-close.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { Dialog } from './dialog.component'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type Dialog } from './dialog.component'; @Directive({ selector: 'button[suiDialogClose]', diff --git a/sushi/src/lib/dialog/dialog-header.directive.ts b/sushi/src/lib/dialog/dialog-header.directive.ts index 2efe5602..4d1ba0c9 100644 --- a/sushi/src/lib/dialog/dialog-header.directive.ts +++ b/sushi/src/lib/dialog/dialog-header.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { ThemeSeverity } from '../sushi.types'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type ThemeSeverity } from '../sushi.types'; @Directive({ selector: 'header[suiDialogHeader]', diff --git a/sushi/src/lib/dialog/dialog-trigger.directive.ts b/sushi/src/lib/dialog/dialog-trigger.directive.ts index 391090bd..731a0af9 100644 --- a/sushi/src/lib/dialog/dialog-trigger.directive.ts +++ b/sushi/src/lib/dialog/dialog-trigger.directive.ts @@ -1,5 +1,13 @@ -import { booleanAttribute, Directive, ElementRef, inject, input, InputSignal, InputSignalWithTransform } from '@angular/core'; -import { Dialog } from './dialog.component'; +import { + booleanAttribute, + Directive, + ElementRef, + inject, + input, + type InputSignal, + type InputSignalWithTransform, +} from '@angular/core'; +import { type Dialog } from './dialog.component'; @Directive({ selector: 'button[suiDialogTrigger]', diff --git a/sushi/src/lib/dialog/dialog.component.ts b/sushi/src/lib/dialog/dialog.component.ts index 93cec3b8..fe080830 100644 --- a/sushi/src/lib/dialog/dialog.component.ts +++ b/sushi/src/lib/dialog/dialog.component.ts @@ -6,22 +6,22 @@ import { Component, computed, DestroyRef, - ElementRef, + type ElementRef, inject, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, viewChild, } from '@angular/core'; import { CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop'; import { LucideX } from '@lucide/angular'; -import { Subscription } from 'rxjs'; -import { DialogCloseEvent, DialogCloseReason, DialogMotion, DialogPosition } from './dialog.interfaces'; +import { type Subscription } from 'rxjs'; +import { type DialogCloseEvent, type DialogCloseReason, type DialogMotion, type DialogPosition } from './dialog.interfaces'; let nextDialogId: number = 0; @@ -145,6 +145,7 @@ export class Dialog { this.open.set(true); return; } + this.restoreTarget = restoreTarget ?? this.activeElement(); this.open.set(true); } @@ -153,6 +154,7 @@ export class Dialog { public close(returnValue: string = '', reason: DialogCloseReason = 'programmatic'): void { const dialog: HTMLDialogElement = this.element().nativeElement; if (!dialog.open || this.pendingClose) return; + this.closeReason = reason; this.open.set(false); if (this.motion() === 'none') { diff --git a/sushi/src/lib/dialog/index.ts b/sushi/src/lib/dialog/index.ts index 7774126b..194f0dbd 100644 --- a/sushi/src/lib/dialog/index.ts +++ b/sushi/src/lib/dialog/index.ts @@ -3,4 +3,4 @@ export * from './dialog-header.directive'; export * from './dialog-slots.directive'; export * from './dialog-trigger.directive'; export * from './dialog.component'; -export * from './dialog.interfaces'; +export type * from './dialog.interfaces'; diff --git a/sushi/src/lib/dialog/testing/dialog.component.spec.ts b/sushi/src/lib/dialog/testing/dialog.component.spec.ts index aaf9b71f..25ddb346 100644 --- a/sushi/src/lib/dialog/testing/dialog.component.spec.ts +++ b/sushi/src/lib/dialog/testing/dialog.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { DialogClose } from '../dialog-close.directive'; @@ -7,7 +7,7 @@ import { DialogHeader } from '../dialog-header.directive'; import { DialogBody, DialogFooter } from '../dialog-slots.directive'; import { DialogTrigger } from '../dialog-trigger.directive'; import { Dialog } from '../dialog.component'; -import { DialogCloseEvent, DialogMotion } from '../dialog.interfaces'; +import { type DialogCloseEvent, type DialogMotion } from '../dialog.interfaces'; @Component({ imports: [Dialog, DialogBody, DialogClose, DialogFooter, DialogHeader, DialogTrigger], @@ -248,7 +248,7 @@ describe('Dialog trigger', (): void => { function pendingExit(dialog: HTMLDialogElement): () => void { let finish!: (animation: Animation) => void; - const finished: Promise = new Promise((resolve: (animation: Animation) => void): void => { + const finished: Promise = new Promise((resolve: (animation: Animation) => void): void => { finish = resolve; }); const exit: Animation = { animationName: 'sui-dialog-exit', finished } as CSSAnimation; diff --git a/sushi/src/lib/divider/divider.directive.ts b/sushi/src/lib/divider/divider.directive.ts index 7df7c7d6..cf321fd9 100644 --- a/sushi/src/lib/divider/divider.directive.ts +++ b/sushi/src/lib/divider/divider.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { DividerOrientation, DividerPlacement, DividerSeverity } from './divider.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type DividerOrientation, type DividerPlacement, type DividerSeverity } from './divider.interfaces'; @Directive({ selector: '[suiDivider]', diff --git a/sushi/src/lib/divider/divider.interfaces.ts b/sushi/src/lib/divider/divider.interfaces.ts index 5405aacd..8a26f246 100644 --- a/sushi/src/lib/divider/divider.interfaces.ts +++ b/sushi/src/lib/divider/divider.interfaces.ts @@ -1,4 +1,4 @@ -import { Orientation, ThemeSeverity } from '../sushi.types'; +import { type Orientation, type ThemeSeverity } from '../sushi.types'; /** Semantic colors available to dividers. */ export type DividerSeverity = ThemeSeverity; diff --git a/sushi/src/lib/divider/index.ts b/sushi/src/lib/divider/index.ts index 12fdc54d..d4633fd2 100644 --- a/sushi/src/lib/divider/index.ts +++ b/sushi/src/lib/divider/index.ts @@ -1,2 +1,2 @@ export * from './divider.directive'; -export * from './divider.interfaces'; +export type * from './divider.interfaces'; diff --git a/sushi/src/lib/divider/testing/divider.directive.spec.ts b/sushi/src/lib/divider/testing/divider.directive.spec.ts index 60be47ee..ebe402a9 100644 --- a/sushi/src/lib/divider/testing/divider.directive.spec.ts +++ b/sushi/src/lib/divider/testing/divider.directive.spec.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Divider } from '../divider.directive'; diff --git a/sushi/src/lib/drawer/drawer-close.directive.ts b/sushi/src/lib/drawer/drawer-close.directive.ts index 4ea764d5..28ac6779 100644 --- a/sushi/src/lib/drawer/drawer-close.directive.ts +++ b/sushi/src/lib/drawer/drawer-close.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { Drawer } from './drawer.component'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type Drawer } from './drawer.component'; @Directive({ selector: 'button[suiDrawerClose]', diff --git a/sushi/src/lib/drawer/drawer-trigger.directive.ts b/sushi/src/lib/drawer/drawer-trigger.directive.ts index ec8dc3f0..828c6191 100644 --- a/sushi/src/lib/drawer/drawer-trigger.directive.ts +++ b/sushi/src/lib/drawer/drawer-trigger.directive.ts @@ -1,5 +1,13 @@ -import { booleanAttribute, Directive, ElementRef, inject, input, InputSignal, InputSignalWithTransform } from '@angular/core'; -import { Drawer } from './drawer.component'; +import { + booleanAttribute, + Directive, + ElementRef, + inject, + input, + type InputSignal, + type InputSignalWithTransform, +} from '@angular/core'; +import { type Drawer } from './drawer.component'; @Directive({ selector: 'button[suiDrawerTrigger]', diff --git a/sushi/src/lib/drawer/drawer.component.ts b/sushi/src/lib/drawer/drawer.component.ts index 9a89e73c..dffe49fe 100644 --- a/sushi/src/lib/drawer/drawer.component.ts +++ b/sushi/src/lib/drawer/drawer.component.ts @@ -1,6 +1,6 @@ import { DOCUMENT } from '@angular/common'; import { CdkTrapFocus } from '@angular/cdk/a11y'; -import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { BreakpointObserver, type BreakpointState } from '@angular/cdk/layout'; import { afterRenderEffect, booleanAttribute, @@ -10,16 +10,16 @@ import { contentChild, inject, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; -import { DrawerCloseReason, DrawerPersistentAt, DrawerPlacement, DrawerSize } from './drawer.interfaces'; +import { type DrawerCloseReason, type DrawerPersistentAt, type DrawerPlacement, type DrawerSize } from './drawer.interfaces'; import { DrawerFooter, DrawerHeader } from './drawer-slots.directive'; let nextDrawerId: number = 0; @@ -85,7 +85,7 @@ export class Drawer { protected readonly persistent: Signal = computed((): boolean => { const breakpoint: DrawerPersistentAt | null = this.persistentAt(); - return breakpoint !== null && this.viewport().breakpoints[DRAWER_BREAKPOINTS[breakpoint]]; + return breakpoint !== null && this.viewport().breakpoints[DRAWER_BREAKPOINTS[breakpoint]] === true; }); protected readonly modalOpen: Signal = computed((): boolean => this.open() && !this.persistent()); diff --git a/sushi/src/lib/drawer/index.ts b/sushi/src/lib/drawer/index.ts index 1a945bd9..87c23bce 100644 --- a/sushi/src/lib/drawer/index.ts +++ b/sushi/src/lib/drawer/index.ts @@ -1,5 +1,5 @@ export * from './drawer-close.directive'; export * from './drawer.component'; -export * from './drawer.interfaces'; +export type * from './drawer.interfaces'; export * from './drawer-slots.directive'; export * from './drawer-trigger.directive'; diff --git a/sushi/src/lib/drawer/testing/drawer.component.spec.ts b/sushi/src/lib/drawer/testing/drawer.component.spec.ts index 295475bf..650b231f 100644 --- a/sushi/src/lib/drawer/testing/drawer.component.spec.ts +++ b/sushi/src/lib/drawer/testing/drawer.component.spec.ts @@ -1,10 +1,10 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { DrawerClose } from '../drawer-close.directive'; import { Drawer } from '../drawer.component'; -import { DrawerCloseReason, DrawerPersistentAt, DrawerPlacement, DrawerSize } from '../drawer.interfaces'; +import { type DrawerCloseReason, type DrawerPersistentAt, type DrawerPlacement, type DrawerSize } from '../drawer.interfaces'; import { DrawerFooter, DrawerHeader } from '../drawer-slots.directive'; import { DrawerTrigger } from '../drawer-trigger.directive'; diff --git a/sushi/src/lib/fieldset/fieldset.directive.ts b/sushi/src/lib/fieldset/fieldset.directive.ts index e3e5e5a6..d2a0717f 100644 --- a/sushi/src/lib/fieldset/fieldset.directive.ts +++ b/sushi/src/lib/fieldset/fieldset.directive.ts @@ -1,4 +1,4 @@ -import { booleanAttribute, Directive, input, InputSignalWithTransform, model, ModelSignal } from '@angular/core'; +import { booleanAttribute, Directive, input, type InputSignalWithTransform, model, type ModelSignal } from '@angular/core'; @Directive({ selector: 'fieldset[suiFieldset]', diff --git a/sushi/src/lib/fieldset/testing/fieldset.directive.spec.ts b/sushi/src/lib/fieldset/testing/fieldset.directive.spec.ts index c5bbcce2..f8524ba3 100644 --- a/sushi/src/lib/fieldset/testing/fieldset.directive.spec.ts +++ b/sushi/src/lib/fieldset/testing/fieldset.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { FieldsetContent } from '../fieldset-content.directive'; diff --git a/sushi/src/lib/file-input/file-drop.component.ts b/sushi/src/lib/file-input/file-drop.component.ts index 4e30c57b..4b61e1f9 100644 --- a/sushi/src/lib/file-input/file-drop.component.ts +++ b/sushi/src/lib/file-input/file-drop.component.ts @@ -5,25 +5,25 @@ import { Component, computed, contentChild, - ElementRef, + type ElementRef, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, signal, TemplateRef, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; +import { type FormValueControl } from '@angular/forms/signals'; import { LucideX } from '@lucide/angular'; import { Button } from '../button'; import { FormControlState } from '../form-control'; -import { FileDropContentContext, FileDropItemContext, FileDropRejection } from './file-drop.interfaces'; +import { type FileDropContentContext, type FileDropItemContext, type FileDropRejection } from './file-drop.interfaces'; import { FileDropContentTemplate, FileDropItemTemplate } from './file-drop.templates'; let nextFileDropId: number = 0; @@ -157,6 +157,7 @@ export class FileDrop extends FormControlState implements FormValueControl itemIndex !== index); this.value.set(files); this.announcement.set(files.length ? files.map((file: File): string => file.name).join(', ') : 'No files selected'); diff --git a/sushi/src/lib/file-input/file-drop.templates.ts b/sushi/src/lib/file-input/file-drop.templates.ts index d5bbfd4c..173edd30 100644 --- a/sushi/src/lib/file-input/file-drop.templates.ts +++ b/sushi/src/lib/file-input/file-drop.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { FileDropContentContext, FileDropItemContext } from './file-drop.interfaces'; +import { type FileDropContentContext, type FileDropItemContext } from './file-drop.interfaces'; /** Replaces the instructional content inside the drop zone. */ @Directive({ selector: 'ng-template[suiFileDropContent]' }) diff --git a/sushi/src/lib/file-input/file-input.interfaces.ts b/sushi/src/lib/file-input/file-input.interfaces.ts index d9b445c6..50f8a2f3 100644 --- a/sushi/src/lib/file-input/file-input.interfaces.ts +++ b/sushi/src/lib/file-input/file-input.interfaces.ts @@ -1,4 +1,4 @@ -import { FormControlSeverity, FormControlSize } from '../form-control'; +import { type FormControlSeverity, type FormControlSize } from '../form-control'; /** Size scale available to native file inputs. */ export type FileInputSize = FormControlSize; diff --git a/sushi/src/lib/file-input/index.ts b/sushi/src/lib/file-input/index.ts index 10fb29a0..eae3ce50 100644 --- a/sushi/src/lib/file-input/index.ts +++ b/sushi/src/lib/file-input/index.ts @@ -1,5 +1,5 @@ export * from './file-drop.component'; -export * from './file-drop.interfaces'; +export type * from './file-drop.interfaces'; export * from './file-drop.templates'; export * from './file-input.directive'; -export * from './file-input.interfaces'; +export type * from './file-input.interfaces'; diff --git a/sushi/src/lib/file-input/testing/file-drop.component.spec.ts b/sushi/src/lib/file-input/testing/file-drop.component.spec.ts index 094c85d2..42bfa83b 100644 --- a/sushi/src/lib/file-input/testing/file-drop.component.spec.ts +++ b/sushi/src/lib/file-input/testing/file-drop.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { query, queryAll, render } from '../../../../testing/test-utils'; +import { query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { FileDrop } from '../file-drop.component'; import type { FileDropRejection } from '../file-drop.interfaces'; import { FileDropContentTemplate, FileDropItemTemplate } from '../file-drop.templates'; @@ -65,7 +65,7 @@ describe('FileDrop native selection', (): void => { expect(fixture.componentInstance.value()).toEqual([image, documentFile]); expect(fixture.componentInstance.rejections).toHaveLength(1); - expect(fixture.componentInstance.rejections[0][0].file).toBe(textFile); + expect(requireItem(requireItem(fixture.componentInstance.rejections, 0), 0).file).toBe(textFile); expect(query(fixture, '[data-file="ramen.png"]').getAttribute('data-index')).toBe('0'); expect(query(fixture, '[data-file="menu.pdf"]').getAttribute('data-size')).toBe('8 B'); }); @@ -82,7 +82,7 @@ describe('FileDrop native selection', (): void => { expect(fixture.componentInstance.value()).toEqual([documentFile]); expect(document.activeElement).toBe(choose); - const clear: HTMLButtonElement = queryAll(fixture, 'button')[1]; + const clear: HTMLButtonElement = requireItem(queryAll(fixture, 'button'), 1); clear.click(); fixture.detectChanges(); diff --git a/sushi/src/lib/file-input/testing/file-input.directive.spec.ts b/sushi/src/lib/file-input/testing/file-input.directive.spec.ts index 568d9fe7..943397d7 100644 --- a/sushi/src/lib/file-input/testing/file-input.directive.spec.ts +++ b/sushi/src/lib/file-input/testing/file-input.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import type { FormControlSeverity, FormControlSize } from '../../form-control'; diff --git a/sushi/src/lib/form-control/fluid-control.state.ts b/sushi/src/lib/form-control/fluid-control.state.ts index b752f07b..d6377e92 100644 --- a/sushi/src/lib/form-control/fluid-control.state.ts +++ b/sushi/src/lib/form-control/fluid-control.state.ts @@ -1,4 +1,4 @@ -import { booleanAttribute, Directive, input, InputSignalWithTransform } from '@angular/core'; +import { booleanAttribute, Directive, input, type InputSignalWithTransform } from '@angular/core'; import { SelectionControlState } from './selection-control.state'; @Directive() diff --git a/sushi/src/lib/form-control/form-control-state.ts b/sushi/src/lib/form-control/form-control-state.ts index a9397d1f..db562158 100644 --- a/sushi/src/lib/form-control/form-control-state.ts +++ b/sushi/src/lib/form-control/form-control-state.ts @@ -1,4 +1,4 @@ -import { booleanAttribute, Directive, input, InputSignalWithTransform } from '@angular/core'; +import { booleanAttribute, Directive, input, type InputSignalWithTransform } from '@angular/core'; @Directive() export abstract class FormControlState { diff --git a/sushi/src/lib/form-control/form-control.interfaces.ts b/sushi/src/lib/form-control/form-control.interfaces.ts index 128b5686..dbfbfa4e 100644 --- a/sushi/src/lib/form-control/form-control.interfaces.ts +++ b/sushi/src/lib/form-control/form-control.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize, ThemeSeverity } from '../sushi.types'; +import { type ComponentSize, type ThemeSeverity } from '../sushi.types'; /** Size scale available to form controls. */ export type FormControlSize = ComponentSize; diff --git a/sushi/src/lib/form-control/index.ts b/sushi/src/lib/form-control/index.ts index b8878cad..cb36a6dd 100644 --- a/sushi/src/lib/form-control/index.ts +++ b/sushi/src/lib/form-control/index.ts @@ -1,4 +1,4 @@ export * from './form-control-state'; -export * from './form-control.interfaces'; +export type * from './form-control.interfaces'; export * from './fluid-control.state'; export * from './selection-control.state'; diff --git a/sushi/src/lib/form-control/selection-control.state.ts b/sushi/src/lib/form-control/selection-control.state.ts index 1a396b0d..9d69dbbf 100644 --- a/sushi/src/lib/form-control/selection-control.state.ts +++ b/sushi/src/lib/form-control/selection-control.state.ts @@ -1,6 +1,6 @@ -import { Directive, input, InputSignal } from '@angular/core'; +import { Directive, input, type InputSignal } from '@angular/core'; import { FormControlState } from './form-control-state'; -import { FormControlSeverity, FormControlSize } from './form-control.interfaces'; +import { type FormControlSeverity, type FormControlSize } from './form-control.interfaces'; @Directive() export abstract class SelectionControlState extends FormControlState { diff --git a/sushi/src/lib/gallery/gallery.component.css b/sushi/src/lib/gallery/gallery.component.css index 71c793a9..ef397700 100644 --- a/sushi/src/lib/gallery/gallery.component.css +++ b/sushi/src/lib/gallery/gallery.component.css @@ -53,7 +53,7 @@ object-fit: cover; } - .sui-gallery[data-fit='contain'] .sui-gallery__image img { + .sui-gallery[data-fit="contain"] .sui-gallery__image img { object-fit: contain; } diff --git a/sushi/src/lib/gallery/gallery.component.ts b/sushi/src/lib/gallery/gallery.component.ts index 6cddb783..2322784d 100644 --- a/sushi/src/lib/gallery/gallery.component.ts +++ b/sushi/src/lib/gallery/gallery.component.ts @@ -4,20 +4,20 @@ import { ChangeDetectionStrategy, Component, computed, - ElementRef, + type ElementRef, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, viewChildren, } from '@angular/core'; import { LucideChevronLeft, LucideChevronRight } from '@lucide/angular'; import { Button } from '../button'; -import { GalleryFit, GalleryImage } from './gallery.interfaces'; +import { type GalleryFit, type GalleryImage } from './gallery.interfaces'; /** Browses an image collection with a large stage and accessible thumbnail navigation. */ @Component({ @@ -166,7 +166,8 @@ export class Gallery { } protected activateImage(event: MouseEvent, image: I): void { - (event.currentTarget as HTMLButtonElement).focus({ preventScroll: true }); + const target: EventTarget | null = event.currentTarget; + if (target instanceof HTMLButtonElement) target.focus({ preventScroll: true }); this.imageActivated.emit(image); } diff --git a/sushi/src/lib/gallery/index.ts b/sushi/src/lib/gallery/index.ts index 5e4c4bc6..b2452c0a 100644 --- a/sushi/src/lib/gallery/index.ts +++ b/sushi/src/lib/gallery/index.ts @@ -1,2 +1,2 @@ export * from './gallery.component'; -export * from './gallery.interfaces'; +export type * from './gallery.interfaces'; diff --git a/sushi/src/lib/gallery/testing/gallery.component.spec.ts b/sushi/src/lib/gallery/testing/gallery.component.spec.ts index 329a4357..ce04d972 100644 --- a/sushi/src/lib/gallery/testing/gallery.component.spec.ts +++ b/sushi/src/lib/gallery/testing/gallery.component.spec.ts @@ -1,8 +1,8 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { query, render } from '../../../../testing/test-utils'; -import { Gallery, GalleryImage } from '../index'; +import { query, render, requireItem } from '../../../../testing/test-utils'; +import { Gallery, type GalleryImage } from '../index'; @Component({ imports: [Gallery], @@ -31,7 +31,7 @@ describe('Gallery', (): void => { const thumbnails: readonly HTMLButtonElement[] = Array.from( (fixture.nativeElement as HTMLElement).querySelectorAll('.sui-gallery__thumbnails button'), ); - thumbnails[1].click(); + requireItem(thumbnails, 1).click(); fixture.detectChanges(); expect(fixture.componentInstance.active()).toBe(1); @@ -51,7 +51,7 @@ describe('Gallery', (): void => { const image: HTMLButtonElement = query(fixture, '.sui-gallery__image') as HTMLButtonElement; image.click(); - expect(fixture.componentInstance.activated).toEqual([fixture.componentInstance.images()[1]]); + expect(fixture.componentInstance.activated).toEqual([requireItem(fixture.componentInstance.images(), 1)]); expect(document.activeElement).toBe(image); }); }); @@ -63,13 +63,13 @@ describe('Gallery keyboard navigation', (): void => { const thumbnails: readonly HTMLButtonElement[] = Array.from( (fixture.nativeElement as HTMLElement).querySelectorAll('.sui-gallery__thumbnails button'), ); - thumbnails[0].focus(); + requireItem(thumbnails, 0).focus(); gallery.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true })); fixture.detectChanges(); expect(fixture.componentInstance.active()).toBe(1); - expect(document.activeElement).toBe(thumbnails[1]); + expect(document.activeElement).toBe(requireItem(thumbnails, 1)); expect(thumbnails.map((thumbnail: HTMLButtonElement): number => thumbnail.tabIndex)).toEqual([-1, 0]); fixture.componentInstance.wrap.set(false); diff --git a/sushi/src/lib/indicator/index.ts b/sushi/src/lib/indicator/index.ts index 4ee6712a..55aa02de 100644 --- a/sushi/src/lib/indicator/index.ts +++ b/sushi/src/lib/indicator/index.ts @@ -1,3 +1,3 @@ export * from './indicator.directive'; -export * from './indicator.interfaces'; +export type * from './indicator.interfaces'; export * from './indicator-item.directive'; diff --git a/sushi/src/lib/indicator/indicator-item.directive.ts b/sushi/src/lib/indicator/indicator-item.directive.ts index 3bd8940c..ef1e93e5 100644 --- a/sushi/src/lib/indicator/indicator-item.directive.ts +++ b/sushi/src/lib/indicator/indicator-item.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { IndicatorHorizontalPosition, IndicatorVerticalPosition } from './indicator.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type IndicatorHorizontalPosition, type IndicatorVerticalPosition } from './indicator.interfaces'; @Directive({ selector: '[suiIndicatorItem]', diff --git a/sushi/src/lib/indicator/testing/indicator.directive.spec.ts b/sushi/src/lib/indicator/testing/indicator.directive.spec.ts index ccf9a999..2f1ff531 100644 --- a/sushi/src/lib/indicator/testing/indicator.directive.spec.ts +++ b/sushi/src/lib/indicator/testing/indicator.directive.spec.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { IndicatorItem } from '../indicator-item.directive'; diff --git a/sushi/src/lib/input-group/input-group.directive.ts b/sushi/src/lib/input-group/input-group.directive.ts index abc11824..daa8c5c6 100644 --- a/sushi/src/lib/input-group/input-group.directive.ts +++ b/sushi/src/lib/input-group/input-group.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { JoinOrientation } from '../join'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type JoinOrientation } from '../join'; /** Layout directions supported by an input group. */ export type InputGroupOrientation = JoinOrientation | 'responsive'; diff --git a/sushi/src/lib/input-group/testing/input-group.directive.spec.ts b/sushi/src/lib/input-group/testing/input-group.directive.spec.ts index 7290c959..0f42f429 100644 --- a/sushi/src/lib/input-group/testing/input-group.directive.spec.ts +++ b/sushi/src/lib/input-group/testing/input-group.directive.spec.ts @@ -1,9 +1,9 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { InputGroupAddon } from '../input-group-addon.directive'; -import { InputGroup, InputGroupOrientation } from '../input-group.directive'; +import { InputGroup, type InputGroupOrientation } from '../input-group.directive'; @Component({ imports: [InputGroup, InputGroupAddon], diff --git a/sushi/src/lib/input-number/index.ts b/sushi/src/lib/input-number/index.ts index 4c69925e..fa6ebda7 100644 --- a/sushi/src/lib/input-number/index.ts +++ b/sushi/src/lib/input-number/index.ts @@ -1,3 +1,3 @@ export * from './input-number.component'; -export * from './input-number.interfaces'; +export type * from './input-number.interfaces'; export * from './input-number.templates'; diff --git a/sushi/src/lib/input-number/input-number.component.ts b/sushi/src/lib/input-number/input-number.component.ts index fb888e1f..08381e65 100644 --- a/sushi/src/lib/input-number/input-number.component.ts +++ b/sushi/src/lib/input-number/input-number.component.ts @@ -5,27 +5,27 @@ import { Component, computed, contentChild, - ElementRef, + type ElementRef, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, numberAttribute, output, - OutputEmitterRef, + type OutputEmitterRef, signal, - Signal, + type Signal, TemplateRef, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; +import { type FormValueControl } from '@angular/forms/signals'; import { LucideChevronDown, LucideChevronUp } from '@lucide/angular'; import { Button } from '../button'; -import { FormControlSize } from '../form-control'; +import { type FormControlSize } from '../form-control'; import { Join, JoinItem } from '../join'; -import { InputNumberButtonsContext } from './input-number.interfaces'; +import { type InputNumberButtonsContext } from './input-number.interfaces'; import { InputNumberButtonsTemplate } from './input-number.templates'; const optionalNumber: (value: unknown) => number | undefined = (value: unknown): number | undefined => @@ -147,7 +147,9 @@ export class InputNumber implements FormValueControl { protected handleInput(event: Event): void { if (this.disabled()) return; - const input: HTMLInputElement = event.currentTarget as HTMLInputElement; + const input: EventTarget | null = event.currentTarget; + if (!(input instanceof HTMLInputElement)) return; + const parts: Intl.NumberFormatPart[] = this.formatter().formatToParts(12345.6); const group: string = parts.find((part): boolean => part.type === 'group')?.value ?? ','; const decimal: string = parts.find((part): boolean => part.type === 'decimal')?.value ?? '.'; diff --git a/sushi/src/lib/input-number/input-number.interfaces.ts b/sushi/src/lib/input-number/input-number.interfaces.ts index c6788d5b..4d648066 100644 --- a/sushi/src/lib/input-number/input-number.interfaces.ts +++ b/sushi/src/lib/input-number/input-number.interfaces.ts @@ -1,4 +1,4 @@ -import { FormControlSize } from '../form-control'; +import { type FormControlSize } from '../form-control'; /** Context exposed to the complete input-number step-control template. */ export interface InputNumberButtonsContext { diff --git a/sushi/src/lib/input-number/input-number.templates.ts b/sushi/src/lib/input-number/input-number.templates.ts index 25965968..2230b5d8 100644 --- a/sushi/src/lib/input-number/input-number.templates.ts +++ b/sushi/src/lib/input-number/input-number.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { InputNumberButtonsContext } from './input-number.interfaces'; +import { type InputNumberButtonsContext } from './input-number.interfaces'; /** Replaces the complete increment and decrement control group. */ @Directive({ selector: 'ng-template[suiInputNumberButtons]' }) diff --git a/sushi/src/lib/input-number/testing/input-number.component.spec.ts b/sushi/src/lib/input-number/testing/input-number.component.spec.ts index a7338717..96f8f8cc 100644 --- a/sushi/src/lib/input-number/testing/input-number.component.spec.ts +++ b/sushi/src/lib/input-number/testing/input-number.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { press, query, queryAll, render } from '../../../../testing/test-utils'; +import { press, query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { InputNumber } from '../input-number.component'; import { InputNumberButtonsTemplate } from '../input-number.templates'; @@ -132,10 +132,10 @@ describe('InputNumber button labels', (): void => { fixture.componentInstance.incrementLabel.set('Aumenta'); fixture.componentInstance.decrementLabel.set('Diminuisci'); fixture.detectChanges(); - buttons[0].click(); + requireItem(buttons, 0).click(); - expect(buttons[0].getAttribute('aria-label')).toBe('Aumenta'); - expect(buttons[1].getAttribute('aria-label')).toBe('Diminuisci'); + expect(requireItem(buttons, 0).getAttribute('aria-label')).toBe('Aumenta'); + expect(requireItem(buttons, 1).getAttribute('aria-label')).toBe('Diminuisci'); expect(fixture.componentInstance.value()).toBe(10); }); }); @@ -145,8 +145,8 @@ describe('InputNumber interaction', (): void => { const fixture: ComponentFixture = render(InputNumberHost); const input: HTMLInputElement = query(fixture, 'input'); const buttons: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - buttons[0].click(); - buttons[0].click(); + requireItem(buttons, 0).click(); + requireItem(buttons, 0).click(); fixture.detectChanges(); expect(fixture.componentInstance.value()).toBe(12); @@ -156,7 +156,7 @@ describe('InputNumber interaction', (): void => { expect(fixture.componentInstance.value()).toBe(10); - buttons[1].click(); + requireItem(buttons, 1).click(); expect(fixture.componentInstance.value()).toBe(8); expect(fixture.componentInstance.touches).toBe(3); diff --git a/sushi/src/lib/input-otp/input-otp.component.ts b/sushi/src/lib/input-otp/input-otp.component.ts index 6f6346f7..c04f92b2 100644 --- a/sushi/src/lib/input-otp/input-otp.component.ts +++ b/sushi/src/lib/input-otp/input-otp.component.ts @@ -3,20 +3,20 @@ import { ChangeDetectionStrategy, Component, computed, - ElementRef, + type ElementRef, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, numberAttribute, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, viewChild, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; -import { FormControlSize } from '../form-control'; +import { type FormValueControl } from '@angular/forms/signals'; +import { type FormControlSize } from '../form-control'; /** Edits a fixed-length one-time code through one accessible native input. */ @Component({ @@ -77,7 +77,9 @@ export class InputOtp implements FormValueControl { protected handleInput(event: Event): void { if (this.disabled()) return; - const inputElement: HTMLInputElement = event.currentTarget as HTMLInputElement; + const inputElement: EventTarget | null = event.currentTarget; + if (!(inputElement instanceof HTMLInputElement)) return; + const nextValue: string = (this.numeric() ? inputElement.value.replace(/\D/g, '') : inputElement.value).slice( 0, this.length(), diff --git a/sushi/src/lib/input-otp/testing/input-otp.component.spec.ts b/sushi/src/lib/input-otp/testing/input-otp.component.spec.ts index 91801f8e..a3da185f 100644 --- a/sushi/src/lib/input-otp/testing/input-otp.component.spec.ts +++ b/sushi/src/lib/input-otp/testing/input-otp.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, queryAll, render } from '../../../../testing/test-utils'; import { InputOtp } from '../input-otp.component'; diff --git a/sushi/src/lib/input-surface/input-surface-control.directive.ts b/sushi/src/lib/input-surface/input-surface-control.directive.ts index f4453b25..f93e4494 100644 --- a/sushi/src/lib/input-surface/input-surface-control.directive.ts +++ b/sushi/src/lib/input-surface/input-surface-control.directive.ts @@ -1,4 +1,4 @@ -import { booleanAttribute, Directive, input, InputSignalWithTransform } from '@angular/core'; +import { booleanAttribute, Directive, input, type InputSignalWithTransform } from '@angular/core'; import { FormControlState } from '../form-control'; @Directive({ diff --git a/sushi/src/lib/input-surface/input-surface.directive.ts b/sushi/src/lib/input-surface/input-surface.directive.ts index 5de9054d..e319371c 100644 --- a/sushi/src/lib/input-surface/input-surface.directive.ts +++ b/sushi/src/lib/input-surface/input-surface.directive.ts @@ -1,5 +1,5 @@ -import { booleanAttribute, Directive, input, InputSignal, InputSignalWithTransform } from '@angular/core'; -import { FormControlSeverity, FormControlSize } from '../form-control'; +import { booleanAttribute, Directive, input, type InputSignal, type InputSignalWithTransform } from '@angular/core'; +import { type FormControlSeverity, type FormControlSize } from '../form-control'; @Directive({ selector: 'div[suiInputSurface]', diff --git a/sushi/src/lib/input-surface/testing/input-surface.directive.spec.ts b/sushi/src/lib/input-surface/testing/input-surface.directive.spec.ts index 173c1050..48ee43c7 100644 --- a/sushi/src/lib/input-surface/testing/input-surface.directive.spec.ts +++ b/sushi/src/lib/input-surface/testing/input-surface.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { InputSurfaceControl } from '../input-surface-control.directive'; diff --git a/sushi/src/lib/input/index.ts b/sushi/src/lib/input/index.ts index 6ff32de4..6f679f9f 100644 --- a/sushi/src/lib/input/index.ts +++ b/sushi/src/lib/input/index.ts @@ -1,2 +1,2 @@ export * from './input.directive'; -export * from './input.interfaces'; +export type * from './input.interfaces'; diff --git a/sushi/src/lib/input/input.directive.ts b/sushi/src/lib/input/input.directive.ts index 6352fda1..8c222126 100644 --- a/sushi/src/lib/input/input.directive.ts +++ b/sushi/src/lib/input/input.directive.ts @@ -1,4 +1,4 @@ -import { booleanAttribute, Directive, input, InputSignalWithTransform } from '@angular/core'; +import { booleanAttribute, Directive, input, type InputSignalWithTransform } from '@angular/core'; import { FluidControlState } from '../form-control'; /** Styles a native input without replacing its browser behavior. */ diff --git a/sushi/src/lib/input/input.interfaces.ts b/sushi/src/lib/input/input.interfaces.ts index 0d4ec143..3f2c8232 100644 --- a/sushi/src/lib/input/input.interfaces.ts +++ b/sushi/src/lib/input/input.interfaces.ts @@ -1,4 +1,4 @@ -import { FormControlSeverity, FormControlSize } from '../form-control'; +import { type FormControlSeverity, type FormControlSize } from '../form-control'; /** Size scale available to native inputs. */ export type InputSize = FormControlSize; diff --git a/sushi/src/lib/input/testing/input.directive.spec.ts b/sushi/src/lib/input/testing/input.directive.spec.ts index 809403ac..1171d022 100644 --- a/sushi/src/lib/input/testing/input.directive.spec.ts +++ b/sushi/src/lib/input/testing/input.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Input } from '../input.directive'; diff --git a/sushi/src/lib/join/index.ts b/sushi/src/lib/join/index.ts index 537cfddd..ca5c41ab 100644 --- a/sushi/src/lib/join/index.ts +++ b/sushi/src/lib/join/index.ts @@ -1,3 +1,3 @@ export * from './join.directive'; -export * from './join.interfaces'; +export type * from './join.interfaces'; export * from './join-item.directive'; diff --git a/sushi/src/lib/join/join.directive.ts b/sushi/src/lib/join/join.directive.ts index e95dd6cd..06ac482c 100644 --- a/sushi/src/lib/join/join.directive.ts +++ b/sushi/src/lib/join/join.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { JoinOrientation } from './join.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type JoinOrientation } from './join.interfaces'; @Directive({ selector: '[suiJoin]', diff --git a/sushi/src/lib/join/join.interfaces.ts b/sushi/src/lib/join/join.interfaces.ts index 52149e56..02d474f4 100644 --- a/sushi/src/lib/join/join.interfaces.ts +++ b/sushi/src/lib/join/join.interfaces.ts @@ -1,4 +1,4 @@ -import { Orientation } from '../sushi.types'; +import { type Orientation } from '../sushi.types'; /** Direction in which joined items are connected. */ export type JoinOrientation = Orientation; diff --git a/sushi/src/lib/join/testing/join.directive.spec.ts b/sushi/src/lib/join/testing/join.directive.spec.ts index 7f90b0d6..f0806969 100644 --- a/sushi/src/lib/join/testing/join.directive.spec.ts +++ b/sushi/src/lib/join/testing/join.directive.spec.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, queryAll, render } from '../../../../testing/test-utils'; import { JoinItem } from '../join-item.directive'; diff --git a/sushi/src/lib/kbd/index.ts b/sushi/src/lib/kbd/index.ts index ff39e344..d4ed1350 100644 --- a/sushi/src/lib/kbd/index.ts +++ b/sushi/src/lib/kbd/index.ts @@ -1,2 +1,2 @@ export * from './kbd.directive'; -export * from './kbd.interfaces'; +export type * from './kbd.interfaces'; diff --git a/sushi/src/lib/kbd/kbd.directive.ts b/sushi/src/lib/kbd/kbd.directive.ts index c3feebaa..dab0d15b 100644 --- a/sushi/src/lib/kbd/kbd.directive.ts +++ b/sushi/src/lib/kbd/kbd.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { KbdSize } from './kbd.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type KbdSize } from './kbd.interfaces'; @Directive({ selector: 'kbd[suiKbd]', diff --git a/sushi/src/lib/kbd/kbd.interfaces.ts b/sushi/src/lib/kbd/kbd.interfaces.ts index 7e2534d4..53db8f51 100644 --- a/sushi/src/lib/kbd/kbd.interfaces.ts +++ b/sushi/src/lib/kbd/kbd.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize } from '../sushi.types'; +import { type ComponentSize } from '../sushi.types'; /** Size scale available to keyboard-key labels. */ export type KbdSize = ComponentSize; diff --git a/sushi/src/lib/kbd/testing/kbd.directive.spec.ts b/sushi/src/lib/kbd/testing/kbd.directive.spec.ts index 348987a4..b7de1294 100644 --- a/sushi/src/lib/kbd/testing/kbd.directive.spec.ts +++ b/sushi/src/lib/kbd/testing/kbd.directive.spec.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Kbd } from '../kbd.directive'; diff --git a/sushi/src/lib/label/label.directive.ts b/sushi/src/lib/label/label.directive.ts index 627da4ff..cc8f8dda 100644 --- a/sushi/src/lib/label/label.directive.ts +++ b/sushi/src/lib/label/label.directive.ts @@ -1,4 +1,4 @@ -import { booleanAttribute, Directive, input, InputSignalWithTransform } from '@angular/core'; +import { booleanAttribute, Directive, input, type InputSignalWithTransform } from '@angular/core'; @Directive({ selector: '[suiLabel]', diff --git a/sushi/src/lib/label/testing/label.directive.spec.ts b/sushi/src/lib/label/testing/label.directive.spec.ts index 309cf125..79015c06 100644 --- a/sushi/src/lib/label/testing/label.directive.spec.ts +++ b/sushi/src/lib/label/testing/label.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Label } from '../label.directive'; diff --git a/sushi/src/lib/lightbox/internal/lightbox-loader.token.ts b/sushi/src/lib/lightbox/internal/lightbox-loader.token.ts index 790053bb..2653731d 100644 --- a/sushi/src/lib/lightbox/internal/lightbox-loader.token.ts +++ b/sushi/src/lib/lightbox/internal/lightbox-loader.token.ts @@ -4,7 +4,7 @@ import type PhotoSwipe from 'photoswipe'; export type LightboxLoader = () => Promise; /** @internal Loads PhotoSwipe only when a Lightbox is opened in the browser. */ -export const LIGHTBOX_LOADER: InjectionToken = new InjectionToken('SUSHI Lightbox loader', { +export const LIGHTBOX_LOADER: InjectionToken = new InjectionToken('SUSHI Lightbox loader', { providedIn: 'root', factory: (): LightboxLoader => async (): Promise => (await import('photoswipe')).default, }); diff --git a/sushi/src/lib/lightbox/lightbox-trigger.directive.ts b/sushi/src/lib/lightbox/lightbox-trigger.directive.ts index 6f21fadc..1e2a1855 100644 --- a/sushi/src/lib/lightbox/lightbox-trigger.directive.ts +++ b/sushi/src/lib/lightbox/lightbox-trigger.directive.ts @@ -1,5 +1,13 @@ -import { booleanAttribute, Directive, ElementRef, inject, input, InputSignal, InputSignalWithTransform } from '@angular/core'; -import { GalleryImage } from '../gallery'; +import { + booleanAttribute, + Directive, + ElementRef, + inject, + input, + type InputSignal, + type InputSignalWithTransform, +} from '@angular/core'; +import { type GalleryImage } from '../gallery'; import { LightboxLauncher } from './internal/lightbox-launcher.service'; /** Opens one native image in a Lightbox. */ diff --git a/sushi/src/lib/list/index.ts b/sushi/src/lib/list/index.ts index 6870409e..dd47ecee 100644 --- a/sushi/src/lib/list/index.ts +++ b/sushi/src/lib/list/index.ts @@ -1,3 +1,3 @@ export * from './list.component'; -export * from './list.interfaces'; +export type * from './list.interfaces'; export * from './list.templates'; diff --git a/sushi/src/lib/list/list.component.ts b/sushi/src/lib/list/list.component.ts index 542b4fc4..e5ec2a02 100644 --- a/sushi/src/lib/list/list.component.ts +++ b/sushi/src/lib/list/list.component.ts @@ -5,12 +5,12 @@ import { Component, contentChild, input, - InputSignal, - InputSignalWithTransform, - Signal, + type InputSignal, + type InputSignalWithTransform, + type Signal, TemplateRef, } from '@angular/core'; -import { ListItemContext, ListSize, ListTrackBy } from './list.interfaces'; +import { type ListItemContext, type ListSize, type ListTrackBy } from './list.interfaces'; import { ListItemTemplate } from './list.templates'; /** Renders a typed collection as a native list with consumer-defined rows. */ diff --git a/sushi/src/lib/list/list.interfaces.ts b/sushi/src/lib/list/list.interfaces.ts index 95d9f5ad..a6696289 100644 --- a/sushi/src/lib/list/list.interfaces.ts +++ b/sushi/src/lib/list/list.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize } from '../sushi.types'; +import { type ComponentSize } from '../sushi.types'; /** Size scale controlling list row density. */ export type ListSize = ComponentSize; diff --git a/sushi/src/lib/list/list.templates.ts b/sushi/src/lib/list/list.templates.ts index 8f227f89..1bdd93b8 100644 --- a/sushi/src/lib/list/list.templates.ts +++ b/sushi/src/lib/list/list.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { ListItemContext } from './list.interfaces'; +import { type ListItemContext } from './list.interfaces'; /** Renders each row and exposes `$implicit`, `item`, `index`, `count`, `first`, `last`, `even`, and `odd`. */ @Directive({ selector: 'ng-template[suiListItem]' }) diff --git a/sushi/src/lib/list/testing/list.component.spec.ts b/sushi/src/lib/list/testing/list.component.spec.ts index 6a5ac4c9..fab3b5a9 100644 --- a/sushi/src/lib/list/testing/list.component.spec.ts +++ b/sushi/src/lib/list/testing/list.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { query, queryAll, render } from '../../../../testing/test-utils'; +import { query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { List } from '../list.component'; import type { ListTrackBy } from '../list.interfaces'; import { ListItemTemplate } from '../list.templates'; @@ -58,8 +58,8 @@ describe('List', (): void => { const fixture: ComponentFixture = render(ListHost); const firstRow: Element = query(fixture, 'li'); fixture.componentInstance.items.update((people: readonly Person[]): readonly Person[] => [ - { id: people[0].id, name: 'Updated' }, - people[1], + { id: requireItem(people, 0).id, name: 'Updated' }, + requireItem(people, 1), ]); fixture.detectChanges(); diff --git a/sushi/src/lib/listbox/index.ts b/sushi/src/lib/listbox/index.ts index 1e1c9c35..dd7a177d 100644 --- a/sushi/src/lib/listbox/index.ts +++ b/sushi/src/lib/listbox/index.ts @@ -1,3 +1,3 @@ export * from './listbox.component'; -export * from './listbox.interfaces'; +export type * from './listbox.interfaces'; export * from './listbox.templates'; diff --git a/sushi/src/lib/listbox/listbox.component.ts b/sushi/src/lib/listbox/listbox.component.ts index 0972c6bd..05636f73 100644 --- a/sushi/src/lib/listbox/listbox.component.ts +++ b/sushi/src/lib/listbox/listbox.component.ts @@ -7,20 +7,20 @@ import { computed, contentChild, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, numberAttribute, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, signal, TemplateRef, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; +import { type FormValueControl } from '@angular/forms/signals'; import { Listbox as AriaListbox, Option as AriaOption } from '@angular/aria/listbox'; import { LucideCheck, LucideSearch } from '@lucide/angular'; import { Button } from '../button'; @@ -30,12 +30,12 @@ import { InputSurface, InputSurfaceControl } from '../input-surface'; import { filterSelectionOption } from '../selection'; import { Spinner } from '../spinner'; import { - ListboxFilterContext, - ListboxGroupContext, - ListboxItemContext, - ListboxModelValue, - ListboxOption, - ListboxValue, + type ListboxFilterContext, + type ListboxGroupContext, + type ListboxItemContext, + type ListboxModelValue, + type ListboxOption, + type ListboxValue, } from './listbox.interfaces'; import { ListboxEmptyFilterTemplate, @@ -190,12 +190,15 @@ export class Listbox } protected handleFocusout(event: FocusEvent): void { - const currentTarget: HTMLElement = event.currentTarget as HTMLElement; - if (!currentTarget.contains(event.relatedTarget as Node | null)) this.touch.emit(); + const currentTarget: EventTarget | null = event.currentTarget; + if (!(currentTarget instanceof HTMLElement)) return; + const destination: EventTarget | null = event.relatedTarget; + if (!(destination instanceof Node) || !currentTarget.contains(destination)) this.touch.emit(); } protected handleFilter(event: Event): void { - this.updateFilter((event.target as HTMLInputElement).value); + const input: EventTarget | null = event.target; + if (input instanceof HTMLInputElement) this.updateFilter(input.value); } protected handleSelectAll(): void { @@ -215,7 +218,8 @@ export class Listbox protected handleScroll(event: Event): void { if (!this.lazy() || this.loading()) return; - const element: HTMLElement = event.currentTarget as HTMLElement; + const element: EventTarget | null = event.currentTarget; + if (!(element instanceof HTMLElement)) return; if (element.scrollHeight - element.scrollTop - element.clientHeight > 48) return; const offset: number = this.options().length; if (this.loadedOffset() === offset) return; diff --git a/sushi/src/lib/listbox/listbox.interfaces.ts b/sushi/src/lib/listbox/listbox.interfaces.ts index b25f53a1..22045d0b 100644 --- a/sushi/src/lib/listbox/listbox.interfaces.ts +++ b/sushi/src/lib/listbox/listbox.interfaces.ts @@ -1,5 +1,5 @@ -import { SelectionFilterContext, SelectionOption } from '../selection'; -import { SelectionValue } from '../sushi.types'; +import { type SelectionFilterContext, type SelectionOption } from '../selection'; +import { type SelectionValue } from '../sushi.types'; /** Value accepted by a listbox option. */ export type ListboxValue = SelectionValue; diff --git a/sushi/src/lib/listbox/listbox.templates.ts b/sushi/src/lib/listbox/listbox.templates.ts index 75d3d264..8ceea0ee 100644 --- a/sushi/src/lib/listbox/listbox.templates.ts +++ b/sushi/src/lib/listbox/listbox.templates.ts @@ -1,4 +1,4 @@ -import { Directive, input, InputSignal } from '@angular/core'; +import { Directive, input, type InputSignal } from '@angular/core'; import type { ListboxFilterContext, ListboxGroupContext, ListboxItemContext, ListboxOption } from './listbox.interfaces'; /** Replaces the filter control and exposes its query, state, and update function. */ diff --git a/sushi/src/lib/listbox/testing/listbox.component.spec.ts b/sushi/src/lib/listbox/testing/listbox.component.spec.ts index 92b873c6..4ab1fb68 100644 --- a/sushi/src/lib/listbox/testing/listbox.component.spec.ts +++ b/sushi/src/lib/listbox/testing/listbox.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { press, query, queryAll, render } from '../../../../testing/test-utils'; import { Listbox } from '../listbox.component'; @@ -39,7 +39,7 @@ const options: readonly RamenOption[] = [ (touch)="touches += 1" > - + { public readonly ariaMenu: Signal> = viewChild.required(AriaMenu); public focus(): void { - this.ariaMenu()._pattern.first(); + const menu: HTMLElement = this.ariaMenu().element; + const firstItem: HTMLElement | null = menu.querySelector( + '[role="menuitem"]:not([aria-disabled="true"]):not([disabled])', + ); + firstItem?.focus(); } protected isGroup(entry: MenuEntry): entry is MenuGroup { diff --git a/sushi/src/lib/menu/internal/menu-surface.component.ts b/sushi/src/lib/menu/internal/menu-surface.component.ts index e1a1cbcc..5ab41534 100644 --- a/sushi/src/lib/menu/internal/menu-surface.component.ts +++ b/sushi/src/lib/menu/internal/menu-surface.component.ts @@ -3,14 +3,21 @@ import { ChangeDetectionStrategy, Component, input, - InputSignal, + type InputSignal, output, - OutputEmitterRef, - Signal, - TemplateRef, + type OutputEmitterRef, + type Signal, + type TemplateRef, viewChild, } from '@angular/core'; -import { MenuEntry, MenuGroupContext, MenuItem, MenuItemContext, MenuSeverity, MenuSize } from '../menu.interfaces'; +import { + type MenuEntry, + type MenuGroupContext, + type MenuItem, + type MenuItemContext, + type MenuSeverity, + type MenuSize, +} from '../menu.interfaces'; import { MenuLevel } from './menu-level.component'; /** @internal */ diff --git a/sushi/src/lib/menu/menu-trigger.directive.ts b/sushi/src/lib/menu/menu-trigger.directive.ts index 8a462c36..0f3356ee 100644 --- a/sushi/src/lib/menu/menu-trigger.directive.ts +++ b/sushi/src/lib/menu/menu-trigger.directive.ts @@ -1,6 +1,14 @@ -import { booleanAttribute, Directive, ElementRef, inject, input, InputSignal, InputSignalWithTransform } from '@angular/core'; -import { Menu } from './menu.component'; -import { MenuItem } from './menu.interfaces'; +import { + booleanAttribute, + Directive, + ElementRef, + inject, + input, + type InputSignal, + type InputSignalWithTransform, +} from '@angular/core'; +import { type Menu } from './menu.component'; +import { type MenuItem } from './menu.interfaces'; /** Opens a popup Menu from a native button. */ @Directive({ diff --git a/sushi/src/lib/menu/menu.component.ts b/sushi/src/lib/menu/menu.component.ts index 687499ad..54bdfbb2 100644 --- a/sushi/src/lib/menu/menu.component.ts +++ b/sushi/src/lib/menu/menu.component.ts @@ -1,5 +1,5 @@ import { DOCUMENT, NgTemplateOutlet } from '@angular/common'; -import { CdkConnectedOverlay, ConnectedPosition, FlexibleConnectedPositionStrategyOrigin } from '@angular/cdk/overlay'; +import { CdkConnectedOverlay, type ConnectedPosition, type FlexibleConnectedPositionStrategyOrigin } from '@angular/cdk/overlay'; import { afterRenderEffect, booleanAttribute, @@ -10,18 +10,26 @@ import { DestroyRef, inject, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, numberAttribute, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, signal, TemplateRef, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; -import { MenuEntry, MenuGroupContext, MenuItem, MenuItemContext, MenuPlacement, MenuSeverity, MenuSize } from './menu.interfaces'; +import { + type MenuEntry, + type MenuGroupContext, + type MenuItem, + type MenuItemContext, + type MenuPlacement, + type MenuSeverity, + type MenuSize, +} from './menu.interfaces'; import { MenuSurface } from './internal/menu-surface.component'; import { MenuEndTemplate, MenuGroupTemplate, MenuItemTemplate, MenuStartTemplate } from './menu.templates'; diff --git a/sushi/src/lib/menu/menu.interfaces.ts b/sushi/src/lib/menu/menu.interfaces.ts index 9d015c4f..5d1a7bd4 100644 --- a/sushi/src/lib/menu/menu.interfaces.ts +++ b/sushi/src/lib/menu/menu.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize, ThemeSeverity } from '../sushi.types'; +import { type ComponentSize, type ThemeSeverity } from '../sushi.types'; /** Primitive value emitted when a menu action is selected. */ export type MenuValue = string | number; diff --git a/sushi/src/lib/menu/menu.templates.ts b/sushi/src/lib/menu/menu.templates.ts index e97bbf1b..2e4a5230 100644 --- a/sushi/src/lib/menu/menu.templates.ts +++ b/sushi/src/lib/menu/menu.templates.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { MenuEntry, MenuGroupContext, MenuItem, MenuItemContext } from './menu.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type MenuEntry, type MenuGroupContext, type MenuItem, type MenuItemContext } from './menu.interfaces'; /** Adds content before all menu entries. */ @Directive({ selector: 'ng-template[suiMenuStart]' }) diff --git a/sushi/src/lib/menu/testing/menu.component.spec.ts b/sushi/src/lib/menu/testing/menu.component.spec.ts index cef9a82f..327f22be 100644 --- a/sushi/src/lib/menu/testing/menu.component.spec.ts +++ b/sushi/src/lib/menu/testing/menu.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, describe, expect, it } from 'vitest'; import { press, query, queryAll, render } from '../../../../testing/test-utils'; import { ContextMenuTrigger } from '../context-menu-trigger.directive'; diff --git a/sushi/src/lib/message/index.ts b/sushi/src/lib/message/index.ts index 4a895f79..95c5dcf6 100644 --- a/sushi/src/lib/message/index.ts +++ b/sushi/src/lib/message/index.ts @@ -1,3 +1,3 @@ export * from './message.directive'; -export * from './message.interfaces'; +export type * from './message.interfaces'; export * from './message-actions.directive'; diff --git a/sushi/src/lib/message/message.directive.ts b/sushi/src/lib/message/message.directive.ts index 5800c479..296cac9e 100644 --- a/sushi/src/lib/message/message.directive.ts +++ b/sushi/src/lib/message/message.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { MessageOrientation, MessageSeverity, MessageVariant } from './message.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type MessageOrientation, type MessageSeverity, type MessageVariant } from './message.interfaces'; @Directive({ selector: '[suiMessage]', diff --git a/sushi/src/lib/message/message.interfaces.ts b/sushi/src/lib/message/message.interfaces.ts index 2db88642..dcd57431 100644 --- a/sushi/src/lib/message/message.interfaces.ts +++ b/sushi/src/lib/message/message.interfaces.ts @@ -1,4 +1,4 @@ -import { Orientation } from '../sushi.types'; +import { type Orientation } from '../sushi.types'; /** Semantic feedback colors available to messages. */ export type MessageSeverity = 'info' | 'success' | 'warning' | 'error'; diff --git a/sushi/src/lib/message/testing/message.directive.spec.ts b/sushi/src/lib/message/testing/message.directive.spec.ts index 7735c69d..0c974d56 100644 --- a/sushi/src/lib/message/testing/message.directive.spec.ts +++ b/sushi/src/lib/message/testing/message.directive.spec.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { MessageActions } from '../message-actions.directive'; diff --git a/sushi/src/lib/multi-select/multi-select.component.ts b/sushi/src/lib/multi-select/multi-select.component.ts index fd551326..b32fcb9c 100644 --- a/sushi/src/lib/multi-select/multi-select.component.ts +++ b/sushi/src/lib/multi-select/multi-select.component.ts @@ -7,39 +7,39 @@ import { computed, contentChild, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, numberAttribute, output, - OutputEmitterRef, + type OutputEmitterRef, signal, - Signal, + type Signal, TemplateRef, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; +import { type FormValueControl } from '@angular/forms/signals'; import { Combobox, ComboboxPopup, ComboboxWidget } from '@angular/aria/combobox'; import { Listbox, Option } from '@angular/aria/listbox'; -import { CdkConnectedOverlay, CdkOverlayOrigin, ConnectedPosition } from '@angular/cdk/overlay'; +import { CdkConnectedOverlay, CdkOverlayOrigin, type ConnectedPosition } from '@angular/cdk/overlay'; import { LucideCheck, LucideChevronDown, LucideX } from '@lucide/angular'; import { Checkbox } from '../checkbox'; -import { FormControlSeverity, FormControlSize, FormControlState } from '../form-control'; +import { type FormControlSeverity, type FormControlSize, FormControlState } from '../form-control'; import { compareSelectionValues, selectionOverlayPositions, SELECTION_BELOW, SELECTION_ABOVE } from '../selection'; import { Spinner } from '../spinner'; import { - MultiSelectCompareWith, - MultiSelectGroupContext, - MultiSelectHeaderContext, - MultiSelectItemContext, - MultiSelectLoadingContext, - MultiSelectModelValue, - MultiSelectOption, - MultiSelectSelectedItemsContext, - MultiSelectValue, - MultiSelectVariant, + type MultiSelectCompareWith, + type MultiSelectGroupContext, + type MultiSelectHeaderContext, + type MultiSelectItemContext, + type MultiSelectLoadingContext, + type MultiSelectModelValue, + type MultiSelectOption, + type MultiSelectSelectedItemsContext, + type MultiSelectValue, + type MultiSelectVariant, } from './multi-select.interfaces'; import { MultiSelectCheckmarkIconTemplate, @@ -193,8 +193,9 @@ export class MultiSelect ); protected readonly displayValue: Signal = computed(() => { const options: readonly O[] = this.selectedOptions(); - if (!options.length) return this.placeholder(); - return options.length === 1 ? options[0].label : `${options[0].label} +${options.length - 1}`; + const first: O | undefined = options[0]; + if (!first) return this.placeholder(); + return options.length === 1 ? first.label : `${first.label} +${options.length - 1}`; }); protected readonly enabledOptions: Signal = computed(() => this.options().filter((option: O): boolean => !option.disabled), @@ -235,6 +236,7 @@ export class MultiSelect if (!this.expanded()) return; const listbox: Listbox | undefined = this.listbox(); if (!listbox?.activeDescendant()) return; + listbox.scrollActiveItemIntoView(); }, }); @@ -255,6 +257,7 @@ export class MultiSelect protected handleExpanded(expanded: boolean): void { if (expanded && (this.disabled() || this.loading())) return; if (this.expanded() === expanded) return; + if (expanded) this.preferAvailableSpace(); this.expanded.set(expanded); if (expanded) return; diff --git a/sushi/src/lib/multi-select/multi-select.interfaces.ts b/sushi/src/lib/multi-select/multi-select.interfaces.ts index 33b6274e..26fbb960 100644 --- a/sushi/src/lib/multi-select/multi-select.interfaces.ts +++ b/sushi/src/lib/multi-select/multi-select.interfaces.ts @@ -1,11 +1,11 @@ import { - SelectCompareWith, - SelectGroupContext, - SelectItemContext, - SelectLoadingContext, - SelectOption, - SelectValue, - SelectVariant, + type SelectCompareWith, + type SelectGroupContext, + type SelectItemContext, + type SelectLoadingContext, + type SelectOption, + type SelectValue, + type SelectVariant, } from '../select'; /** Value accepted by a multi-select option. */ diff --git a/sushi/src/lib/multi-select/multi-select.templates.ts b/sushi/src/lib/multi-select/multi-select.templates.ts index 0637bf0c..dcc4b247 100644 --- a/sushi/src/lib/multi-select/multi-select.templates.ts +++ b/sushi/src/lib/multi-select/multi-select.templates.ts @@ -1,11 +1,11 @@ -import { Directive, input, InputSignal } from '@angular/core'; +import { Directive, input, type InputSignal } from '@angular/core'; import { - MultiSelectGroupContext, - MultiSelectHeaderContext, - MultiSelectItemContext, - MultiSelectLoadingContext, - MultiSelectOption, - MultiSelectSelectedItemsContext, + type MultiSelectGroupContext, + type MultiSelectHeaderContext, + type MultiSelectItemContext, + type MultiSelectLoadingContext, + type MultiSelectOption, + type MultiSelectSelectedItemsContext, } from './multi-select.interfaces'; /** Replaces each option and exposes its index, selected, and disabled state. */ diff --git a/sushi/src/lib/multi-select/testing/multi-select.component.spec.ts b/sushi/src/lib/multi-select/testing/multi-select.component.spec.ts index 31dfae49..a94e93e7 100644 --- a/sushi/src/lib/multi-select/testing/multi-select.component.spec.ts +++ b/sushi/src/lib/multi-select/testing/multi-select.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { MultiSelect } from '../multi-select.component'; diff --git a/sushi/src/lib/navbar/index.ts b/sushi/src/lib/navbar/index.ts index 4e072c24..fdbbe068 100644 --- a/sushi/src/lib/navbar/index.ts +++ b/sushi/src/lib/navbar/index.ts @@ -1,5 +1,5 @@ export * from './navbar.component'; export * from './navbar-content.directive'; -export * from './navbar.interfaces'; +export type * from './navbar.interfaces'; export * from './navbar-item-template.directive'; export * from './navbar-slot.directive'; diff --git a/sushi/src/lib/navbar/internal/navbar-links.directive.ts b/sushi/src/lib/navbar/internal/navbar-links.directive.ts index 44266d26..09dbf78e 100644 --- a/sushi/src/lib/navbar/internal/navbar-links.directive.ts +++ b/sushi/src/lib/navbar/internal/navbar-links.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { NavbarOrientation, NavbarVariant } from '../navbar.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type NavbarOrientation, type NavbarVariant } from '../navbar.interfaces'; /** * Arranges links rendered by the internal Navbar menu. diff --git a/sushi/src/lib/navbar/internal/navbar-menu.component.ts b/sushi/src/lib/navbar/internal/navbar-menu.component.ts index 777f7b95..8dfab3b1 100644 --- a/sushi/src/lib/navbar/internal/navbar-menu.component.ts +++ b/sushi/src/lib/navbar/internal/navbar-menu.component.ts @@ -4,14 +4,14 @@ import { Component, inject, input, - InputSignal, + type InputSignal, output, - OutputEmitterRef, - TemplateRef, + type OutputEmitterRef, + type TemplateRef, } from '@angular/core'; import { LucideChevronDown } from '@lucide/angular'; import { RouterLink } from '@angular/router'; -import { NavbarItem, NavbarItemContext, NavbarOrientation, NavbarVariant } from '../navbar.interfaces'; +import { type NavbarItem, type NavbarItemContext, type NavbarOrientation, type NavbarVariant } from '../navbar.interfaces'; import { NavbarDropdown } from './navbar-dropdown.directive'; import { NavbarLink } from './navbar-link.directive'; import { NavbarLinks } from './navbar-links.directive'; diff --git a/sushi/src/lib/navbar/internal/navbar.state.ts b/sushi/src/lib/navbar/internal/navbar.state.ts index 7a12a100..a7d5d612 100644 --- a/sushi/src/lib/navbar/internal/navbar.state.ts +++ b/sushi/src/lib/navbar/internal/navbar.state.ts @@ -1,4 +1,4 @@ -import { Injectable, signal, WritableSignal } from '@angular/core'; +import { Injectable, signal, type WritableSignal } from '@angular/core'; let nextNavbarId: number = 0; diff --git a/sushi/src/lib/navbar/navbar-item-template.directive.ts b/sushi/src/lib/navbar/navbar-item-template.directive.ts index abd62001..3ee1102f 100644 --- a/sushi/src/lib/navbar/navbar-item-template.directive.ts +++ b/sushi/src/lib/navbar/navbar-item-template.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { NavbarItem, NavbarItemContext } from './navbar.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type NavbarItem, type NavbarItemContext } from './navbar.interfaces'; /** Customizes the visible content of every item rendered from the Navbar model. */ @Directive({ selector: 'ng-template[suiNavbarItem]' }) diff --git a/sushi/src/lib/navbar/navbar.component.ts b/sushi/src/lib/navbar/navbar.component.ts index 4693d204..c832546a 100644 --- a/sushi/src/lib/navbar/navbar.component.ts +++ b/sushi/src/lib/navbar/navbar.component.ts @@ -7,11 +7,11 @@ import { contentChildren, inject, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, TemplateRef, } from '@angular/core'; import { LucideMenu } from '@lucide/angular'; @@ -22,15 +22,15 @@ import { NavbarToggle } from './internal/navbar-toggle.directive'; import { NavbarContent } from './navbar-content.directive'; import { NavbarItemTemplate } from './navbar-item-template.directive'; import { - NavbarCollapseAt, - NavbarInset, - NavbarItem, - NavbarItemContext, - NavbarMenuAlign, - NavbarOrientation, - NavbarSize, - NavbarSurface, - NavbarVariant, + type NavbarCollapseAt, + type NavbarInset, + type NavbarItem, + type NavbarItemContext, + type NavbarMenuAlign, + type NavbarOrientation, + type NavbarSize, + type NavbarSurface, + type NavbarVariant, } from './navbar.interfaces'; import { NavbarAction, NavbarBrand } from './navbar-slot.directive'; diff --git a/sushi/src/lib/navbar/navbar.interfaces.ts b/sushi/src/lib/navbar/navbar.interfaces.ts index eaac01ae..b992b321 100644 --- a/sushi/src/lib/navbar/navbar.interfaces.ts +++ b/sushi/src/lib/navbar/navbar.interfaces.ts @@ -1,4 +1,4 @@ -import { Orientation } from '../sushi.types'; +import { type Orientation } from '../sushi.types'; /** Supported Navbar link arrangements. */ export type NavbarOrientation = Orientation | 'responsive'; diff --git a/sushi/src/lib/navbar/testing/navbar.component.spec.ts b/sushi/src/lib/navbar/testing/navbar.component.spec.ts index 8922a50e..f9e3c5cc 100644 --- a/sushi/src/lib/navbar/testing/navbar.component.spec.ts +++ b/sushi/src/lib/navbar/testing/navbar.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { press, query, queryAll, render } from '../../../../testing/test-utils'; +import { press, query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { NavbarContent } from '../navbar-content.directive'; import { NavbarItemTemplate } from '../navbar-item-template.directive'; import { Navbar } from '../navbar.component'; @@ -148,8 +148,8 @@ describe('Navbar disclosures', (): void => { it('keeps only the most recently opened disclosure expanded', (): void => { const fixture: ComponentFixture = render(NavbarHost); const details: readonly Element[] = queryAll(fixture, 'details'); - const first: HTMLDetailsElement = details[0] as HTMLDetailsElement; - const second: HTMLDetailsElement = details[1] as HTMLDetailsElement; + const first: HTMLDetailsElement = requireItem(details, 0) as HTMLDetailsElement; + const second: HTMLDetailsElement = requireItem(details, 1) as HTMLDetailsElement; first.open = true; first.dispatchEvent(new Event('toggle')); second.open = true; @@ -173,7 +173,7 @@ describe('Navbar disclosures', (): void => { it('does not open a disabled disclosure', (): void => { const fixture: ComponentFixture = render(NavbarHost); const summaries: readonly Element[] = queryAll(fixture, 'summary'); - const disabled: HTMLElement = summaries[2] as HTMLElement; + const disabled: HTMLElement = requireItem(summaries, 2) as HTMLElement; disabled.click(); expect(disabled.getAttribute('aria-disabled')).toBe('true'); diff --git a/sushi/src/lib/order-list/index.ts b/sushi/src/lib/order-list/index.ts index 0aa11b16..a1cef634 100644 --- a/sushi/src/lib/order-list/index.ts +++ b/sushi/src/lib/order-list/index.ts @@ -1,3 +1,3 @@ export * from './order-list.component'; -export * from './order-list.interfaces'; +export type * from './order-list.interfaces'; export * from './order-list.templates'; diff --git a/sushi/src/lib/order-list/order-list.component.css b/sushi/src/lib/order-list/order-list.component.css index 04ad18ba..96f3adbb 100644 --- a/sushi/src/lib/order-list/order-list.component.css +++ b/sushi/src/lib/order-list/order-list.component.css @@ -30,7 +30,7 @@ min-width: 0; } -.cdk-drag-preview.sui-order-list-row[data-selected='true'] { +.cdk-drag-preview.sui-order-list-row[data-selected="true"] { color: var(--color-primary); background: color-mix(in oklab, var(--color-primary) 12%, var(--color-base-100)); } diff --git a/sushi/src/lib/order-list/order-list.component.ts b/sushi/src/lib/order-list/order-list.component.ts index acc5358e..fea1af33 100644 --- a/sushi/src/lib/order-list/order-list.component.ts +++ b/sushi/src/lib/order-list/order-list.component.ts @@ -1,6 +1,6 @@ import { NgTemplateOutlet } from '@angular/common'; import { Listbox as AriaListbox, Option as AriaOption } from '@angular/aria/listbox'; -import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop'; +import { CdkDrag, type CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop'; import { CdkScrollable } from '@angular/cdk/scrolling'; import { afterRenderEffect, @@ -10,26 +10,31 @@ import { computed, contentChild, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, numberAttribute, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, signal, TemplateRef, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; +import { type FormValueControl } from '@angular/forms/signals'; import { LucideChevronDown, LucideChevronUp, LucideChevronsDown, LucideChevronsUp, LucideSearch } from '@lucide/angular'; import { Button } from '../button'; import { FormControlState } from '../form-control'; import { InputSurface, InputSurfaceControl } from '../input-surface'; import { filterSelectionOption } from '../selection'; -import { OrderListFilterContext, OrderListItemContext, OrderListOption, OrderListValue } from './order-list.interfaces'; +import { + type OrderListFilterContext, + type OrderListItemContext, + type OrderListOption, + type OrderListValue, +} from './order-list.interfaces'; import { OrderListFilterTemplate, OrderListHeaderTemplate, OrderListItemTemplate } from './order-list.templates'; /** Sorts a collection with selection controls or optional drag and drop. */ @@ -176,21 +181,29 @@ export class OrderList protected moveUp(): void { if (this.disabled() || !this.canMoveUp()) return; + const items: I[] = [...this.value()]; const selected: ReadonlySet = new Set(this.selection()); + for (const index of this.selectedIndexes()) { - if (index > 0 && !selected.has(items[index - 1].value)) moveItemInArray(items, index, index - 1); + const previous: I | undefined = items[index - 1]; + if (previous && !selected.has(previous.value)) moveItemInArray(items, index, index - 1); } + this.commit(items); } protected moveDown(): void { if (this.disabled() || !this.canMoveDown()) return; + const items: I[] = [...this.value()]; const selected: ReadonlySet = new Set(this.selection()); + for (const index of [...this.selectedIndexes()].reverse()) { - if (index < items.length - 1 && !selected.has(items[index + 1].value)) moveItemInArray(items, index, index + 1); + const next: I | undefined = items[index + 1]; + if (next && !selected.has(next.value)) moveItemInArray(items, index, index + 1); } + this.commit(items); } @@ -234,7 +247,8 @@ export class OrderList } protected handleFilter(event: Event): void { - this.updateFilter((event.target as HTMLInputElement).value); + const input: EventTarget | null = event.target; + if (input instanceof HTMLInputElement) this.updateFilter(input.value); } protected handleDrop(event: CdkDragDrop): void { @@ -250,8 +264,10 @@ export class OrderList } protected handleFocusout(event: FocusEvent): void { - const currentTarget: HTMLElement = event.currentTarget as HTMLElement; - if (!currentTarget.contains(event.relatedTarget as Node | null)) this.touch.emit(); + const currentTarget: EventTarget | null = event.currentTarget; + if (!(currentTarget instanceof HTMLElement)) return; + const destination: EventTarget | null = event.relatedTarget; + if (!(destination instanceof Node) || !currentTarget.contains(destination)) this.touch.emit(); } protected itemContext(item: I, index: number): OrderListItemContext { diff --git a/sushi/src/lib/order-list/order-list.interfaces.ts b/sushi/src/lib/order-list/order-list.interfaces.ts index bd30e271..08e221ea 100644 --- a/sushi/src/lib/order-list/order-list.interfaces.ts +++ b/sushi/src/lib/order-list/order-list.interfaces.ts @@ -1,5 +1,5 @@ -import { SelectionFilterContext, SelectionOption } from '../selection'; -import { SelectionValue } from '../sushi.types'; +import { type SelectionFilterContext, type SelectionOption } from '../selection'; +import { type SelectionValue } from '../sushi.types'; /** Value that uniquely identifies an order-list item. */ export type OrderListValue = SelectionValue; diff --git a/sushi/src/lib/order-list/order-list.templates.ts b/sushi/src/lib/order-list/order-list.templates.ts index 8cf12f7d..66f502a2 100644 --- a/sushi/src/lib/order-list/order-list.templates.ts +++ b/sushi/src/lib/order-list/order-list.templates.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { OrderListFilterContext, OrderListItemContext, OrderListOption } from './order-list.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type OrderListFilterContext, type OrderListItemContext, type OrderListOption } from './order-list.interfaces'; /** Replaces the filter control and exposes its query, state, and update function. */ @Directive({ selector: 'ng-template[suiOrderListFilter]' }) diff --git a/sushi/src/lib/order-list/testing/order-list.component.spec.ts b/sushi/src/lib/order-list/testing/order-list.component.spec.ts index 5e797472..3df7bb43 100644 --- a/sushi/src/lib/order-list/testing/order-list.component.spec.ts +++ b/sushi/src/lib/order-list/testing/order-list.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { press, query, queryAll, render } from '../../../../testing/test-utils'; import { OrderList } from '../order-list.component'; @@ -32,7 +32,7 @@ const items: readonly WorkItem[] = [ > Workflow steps - + diff --git a/sushi/src/lib/pagination/index.ts b/sushi/src/lib/pagination/index.ts index 9701f97b..6c5a1df9 100644 --- a/sushi/src/lib/pagination/index.ts +++ b/sushi/src/lib/pagination/index.ts @@ -1,3 +1,3 @@ export * from './pagination.component'; -export * from './pagination.interfaces'; +export type * from './pagination.interfaces'; export * from './pagination.templates'; diff --git a/sushi/src/lib/pagination/pagination.component.css b/sushi/src/lib/pagination/pagination.component.css index 654b9b1e..9758b86a 100644 --- a/sushi/src/lib/pagination/pagination.component.css +++ b/sushi/src/lib/pagination/pagination.component.css @@ -16,7 +16,7 @@ } @media (hover: hover) { - :host(.sui-pagination--plain) .sui-pagination__action:not([aria-disabled='true']):is(:hover, :focus-visible) { + :host(.sui-pagination--plain) .sui-pagination__action:not([aria-disabled="true"]):is(:hover, :focus-visible) { color: var(--btn-color, var(--color-primary)); background-color: color-mix(in oklab, var(--btn-color, var(--color-primary)) var(--sui-pagination-hover-mix), transparent); border-color: transparent; diff --git a/sushi/src/lib/pagination/pagination.component.ts b/sushi/src/lib/pagination/pagination.component.ts index e89656c7..f717d21c 100644 --- a/sushi/src/lib/pagination/pagination.component.ts +++ b/sushi/src/lib/pagination/pagination.component.ts @@ -6,14 +6,14 @@ import { computed, contentChild, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, - Signal, + type ModelSignal, + type Signal, signal, TemplateRef, - WritableSignal, + type WritableSignal, } from '@angular/core'; import { LucideChevronLeft, LucideChevronRight, LucideChevronsLeft, LucideChevronsRight } from '@lucide/angular'; import { AutoFocus } from '../auto-focus'; diff --git a/sushi/src/lib/pagination/pagination.interfaces.ts b/sushi/src/lib/pagination/pagination.interfaces.ts index 22a5f533..5609b00c 100644 --- a/sushi/src/lib/pagination/pagination.interfaces.ts +++ b/sushi/src/lib/pagination/pagination.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize, ThemeSeverity } from '../sushi.types'; +import { type ComponentSize, type ThemeSeverity } from '../sushi.types'; /** Size scale available to pagination controls. */ export type PaginationSize = ComponentSize; diff --git a/sushi/src/lib/pagination/pagination.templates.ts b/sushi/src/lib/pagination/pagination.templates.ts index 0954c4c2..d6c3a6c3 100644 --- a/sushi/src/lib/pagination/pagination.templates.ts +++ b/sushi/src/lib/pagination/pagination.templates.ts @@ -1,5 +1,9 @@ import { Directive } from '@angular/core'; -import { PaginationNavigationContext, PaginationPageContext, PaginationReportContext } from './pagination.interfaces'; +import { + type PaginationNavigationContext, + type PaginationPageContext, + type PaginationReportContext, +} from './pagination.interfaces'; /** Replaces the content of every numbered page action. */ @Directive({ selector: 'ng-template[suiPaginationPage]' }) diff --git a/sushi/src/lib/pagination/testing/pagination.component.spec.ts b/sushi/src/lib/pagination/testing/pagination.component.spec.ts index 0d0d23e5..1046a158 100644 --- a/sushi/src/lib/pagination/testing/pagination.component.spec.ts +++ b/sushi/src/lib/pagination/testing/pagination.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, describe, expect, it } from 'vitest'; import { press, query, queryAll, render } from '../../../../testing/test-utils'; import { Pagination } from '../pagination.component'; diff --git a/sushi/src/lib/popover/index.ts b/sushi/src/lib/popover/index.ts index 46a0bc1c..5c228493 100644 --- a/sushi/src/lib/popover/index.ts +++ b/sushi/src/lib/popover/index.ts @@ -1,4 +1,4 @@ export * from './popover-close.directive'; export * from './popover-trigger.directive'; export * from './popover.component'; -export * from './popover.interfaces'; +export type * from './popover.interfaces'; diff --git a/sushi/src/lib/popover/popover-close.directive.ts b/sushi/src/lib/popover/popover-close.directive.ts index 97734c09..d0f5676d 100644 --- a/sushi/src/lib/popover/popover-close.directive.ts +++ b/sushi/src/lib/popover/popover-close.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { Popover } from './popover.component'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type Popover } from './popover.component'; /** Closes a Popover from a native button. */ @Directive({ selector: 'button[suiPopoverClose]', host: { type: 'button', '(click)': 'popover().hide(true)' } }) diff --git a/sushi/src/lib/popover/popover-trigger.directive.ts b/sushi/src/lib/popover/popover-trigger.directive.ts index 87a8ae1a..69b7f34f 100644 --- a/sushi/src/lib/popover/popover-trigger.directive.ts +++ b/sushi/src/lib/popover/popover-trigger.directive.ts @@ -1,5 +1,13 @@ -import { booleanAttribute, Directive, ElementRef, inject, input, InputSignal, InputSignalWithTransform } from '@angular/core'; -import { Popover } from './popover.component'; +import { + booleanAttribute, + Directive, + ElementRef, + inject, + input, + type InputSignal, + type InputSignalWithTransform, +} from '@angular/core'; +import { type Popover } from './popover.component'; /** Opens a Popover from a native button. */ @Directive({ diff --git a/sushi/src/lib/popover/popover.component.css b/sushi/src/lib/popover/popover.component.css index d907b148..2ad7f2c5 100644 --- a/sushi/src/lib/popover/popover.component.css +++ b/sushi/src/lib/popover/popover.component.css @@ -34,33 +34,33 @@ position: absolute; width: 0.625rem; height: 0.625rem; - content: ''; + content: ""; background: var(--sui-popover-background); transform: rotate(45deg); } - .sui-popover[data-placement='bottom']::before { + .sui-popover[data-placement="bottom"]::before { top: -0.375rem; left: calc(var(--_sui-popover-arrow-x, 50%) - 0.3125rem); border-top: var(--sui-popover-border-width) solid var(--sui-popover-border-color); border-left: var(--sui-popover-border-width) solid var(--sui-popover-border-color); } - .sui-popover[data-placement='top']::before { + .sui-popover[data-placement="top"]::before { bottom: -0.375rem; left: calc(var(--_sui-popover-arrow-x, 50%) - 0.3125rem); border-right: var(--sui-popover-border-width) solid var(--sui-popover-border-color); border-bottom: var(--sui-popover-border-width) solid var(--sui-popover-border-color); } - .sui-popover[data-placement='right']::before { + .sui-popover[data-placement="right"]::before { top: calc(var(--_sui-popover-arrow-y, 50%) - 0.3125rem); left: -0.375rem; border-bottom: var(--sui-popover-border-width) solid var(--sui-popover-border-color); border-left: var(--sui-popover-border-width) solid var(--sui-popover-border-color); } - .sui-popover[data-placement='left']::before { + .sui-popover[data-placement="left"]::before { top: calc(var(--_sui-popover-arrow-y, 50%) - 0.3125rem); right: -0.375rem; border-top: var(--sui-popover-border-width) solid var(--sui-popover-border-color); diff --git a/sushi/src/lib/popover/popover.component.ts b/sushi/src/lib/popover/popover.component.ts index 314b6b6a..8494c883 100644 --- a/sushi/src/lib/popover/popover.component.ts +++ b/sushi/src/lib/popover/popover.component.ts @@ -1,8 +1,8 @@ import { CdkConnectedOverlay, - ConnectedOverlayPositionChange, - ConnectedPosition, - FlexibleConnectedPositionStrategyOrigin, + type ConnectedOverlayPositionChange, + type ConnectedPosition, + type FlexibleConnectedPositionStrategyOrigin, } from '@angular/cdk/overlay'; import { LucideX } from '@lucide/angular'; import { @@ -11,19 +11,19 @@ import { Component, computed, input, - ElementRef, - InputSignal, - InputSignalWithTransform, + type ElementRef, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, numberAttribute, - Signal, + type Signal, signal, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; import { Button } from '../button'; -import { PopoverPlacement } from './popover.interfaces'; +import { type PopoverPlacement } from './popover.interfaces'; let nextPopoverId: number = 0; diff --git a/sushi/src/lib/popover/testing/popover.component.spec.ts b/sushi/src/lib/popover/testing/popover.component.spec.ts index 7828aeec..2e80a56b 100644 --- a/sushi/src/lib/popover/testing/popover.component.spec.ts +++ b/sushi/src/lib/popover/testing/popover.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, describe, expect, it } from 'vitest'; import { query, render, press } from '../../../../testing/test-utils'; import { Popover } from '../popover.component'; diff --git a/sushi/src/lib/progress/progress.directive.ts b/sushi/src/lib/progress/progress.directive.ts index 0fcd6633..1f92c28a 100644 --- a/sushi/src/lib/progress/progress.directive.ts +++ b/sushi/src/lib/progress/progress.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { ProgressAnimation, ProgressSeverity } from './progress.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type ProgressAnimation, type ProgressSeverity } from './progress.interfaces'; /** Styles a native progress element without replacing its value, maximum, or accessibility semantics. */ @Directive({ diff --git a/sushi/src/lib/progress/progress.interfaces.ts b/sushi/src/lib/progress/progress.interfaces.ts index 8f12e8bf..e82de3dd 100644 --- a/sushi/src/lib/progress/progress.interfaces.ts +++ b/sushi/src/lib/progress/progress.interfaces.ts @@ -1,4 +1,4 @@ -import { ThemeSeverity } from '../sushi.types'; +import { type ThemeSeverity } from '../sushi.types'; /** Semantic colors available to progress indicators. */ export type ProgressSeverity = ThemeSeverity; diff --git a/sushi/src/lib/progress/testing/progress.directive.spec.ts b/sushi/src/lib/progress/testing/progress.directive.spec.ts index 32324b74..ff35e79f 100644 --- a/sushi/src/lib/progress/testing/progress.directive.spec.ts +++ b/sushi/src/lib/progress/testing/progress.directive.spec.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Progress } from '../progress.directive'; diff --git a/sushi/src/lib/radio/testing/radio.directive.spec.ts b/sushi/src/lib/radio/testing/radio.directive.spec.ts index abff30da..70da5525 100644 --- a/sushi/src/lib/radio/testing/radio.directive.spec.ts +++ b/sushi/src/lib/radio/testing/radio.directive.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { queryAll, render } from '../../../../testing/test-utils'; +import { queryAll, render, requireItem } from '../../../../testing/test-utils'; import { Radio } from '../radio.directive'; @Component({ @@ -20,8 +20,8 @@ describe('Radio', (): void => { it('maps appearance and delayed invalid state', (): void => { const fixture: ComponentFixture = render(RadioHost); const radios: readonly HTMLInputElement[] = queryAll(fixture, 'input'); - const first: HTMLInputElement = radios[0]; - const second: HTMLInputElement = radios[1]; + const first: HTMLInputElement = requireItem(radios, 0); + const second: HTMLInputElement = requireItem(radios, 1); expect(second.classList).toContain('radio-warning'); expect(second.classList).toContain('radio-sm'); diff --git a/sushi/src/lib/range/range.directive.ts b/sushi/src/lib/range/range.directive.ts index f7fc784e..999ccddf 100644 --- a/sushi/src/lib/range/range.directive.ts +++ b/sushi/src/lib/range/range.directive.ts @@ -1,5 +1,5 @@ import { afterRenderEffect, Directive, ElementRef, inject } from '@angular/core'; -import { FORM_FIELD, FormField } from '@angular/forms/signals'; +import { FORM_FIELD, type FormField } from '@angular/forms/signals'; import { SelectionControlState } from '../form-control'; /** Styles a native range control and reflects its current progress. */ diff --git a/sushi/src/lib/range/testing/range.directive.spec.ts b/sushi/src/lib/range/testing/range.directive.spec.ts index e3cac5f9..72955a16 100644 --- a/sushi/src/lib/range/testing/range.directive.spec.ts +++ b/sushi/src/lib/range/testing/range.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Range } from '../range.directive'; diff --git a/sushi/src/lib/select-button/index.ts b/sushi/src/lib/select-button/index.ts index 80bcbe90..5ac67598 100644 --- a/sushi/src/lib/select-button/index.ts +++ b/sushi/src/lib/select-button/index.ts @@ -1,3 +1,3 @@ export * from './select-button.component'; -export * from './select-button.interfaces'; +export type * from './select-button.interfaces'; export * from './select-button.templates'; diff --git a/sushi/src/lib/select-button/select-button.component.ts b/sushi/src/lib/select-button/select-button.component.ts index d3af0194..c82b70fa 100644 --- a/sushi/src/lib/select-button/select-button.component.ts +++ b/sushi/src/lib/select-button/select-button.component.ts @@ -5,25 +5,25 @@ import { Component, contentChild, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, TemplateRef, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; +import { type FormValueControl } from '@angular/forms/signals'; import { Button } from '../button'; import { JoinItem } from '../join'; import { - SelectButtonOption, - SelectButtonOptionContext, - SelectButtonOrientation, - SelectButtonSeverity, - SelectButtonSize, - SelectButtonValue, + type SelectButtonOption, + type SelectButtonOptionContext, + type SelectButtonOrientation, + type SelectButtonSeverity, + type SelectButtonSize, + type SelectButtonValue, } from './select-button.interfaces'; import { SelectButtonOptionTemplate } from './select-button.templates'; @@ -119,19 +119,24 @@ export class SelectButton implements FormValueControl protected handleKeydown(event: KeyboardEvent, index: number): void { const keys: Readonly>> = this.orientation() === 'vertical' ? VERTICAL_KEYS : HORIZONTAL_KEYS; const direction: number | undefined = keys[event.key]; - const edge: number | undefined = event.key === 'Home' ? 0 : event.key === 'End' ? this.options().length - 1 : undefined; + let edge: number | undefined; + if (event.key === 'Home') edge = 0; + else if (event.key === 'End') edge = this.options().length - 1; if (direction === undefined && edge === undefined) return; event.preventDefault(); const options: readonly SelectButtonOption[] = this.options(); const nextIndex: number = edge ?? (index + (direction ?? 0) + options.length) % options.length; + const option: SelectButtonOption | undefined = options[nextIndex]; + if (!option) return; + const target: EventTarget | null = event.currentTarget; if (!(target instanceof HTMLElement)) return; const parent: HTMLElement | null = target.parentElement; if (!parent) return; const buttons: NodeListOf = parent.querySelectorAll('button[role="radio"]'); buttons.item(nextIndex).focus(); - this.handleSelection(options[nextIndex]); + this.handleSelection(option); } } diff --git a/sushi/src/lib/select-button/select-button.interfaces.ts b/sushi/src/lib/select-button/select-button.interfaces.ts index aa1b2f4b..203da3ee 100644 --- a/sushi/src/lib/select-button/select-button.interfaces.ts +++ b/sushi/src/lib/select-button/select-button.interfaces.ts @@ -1,6 +1,6 @@ -import { ButtonSeverity, ButtonSize } from '../button'; -import { SelectionOption } from '../selection'; -import { Orientation, SelectionValue } from '../sushi.types'; +import { type ButtonSeverity, type ButtonSize } from '../button'; +import { type SelectionOption } from '../selection'; +import { type Orientation, type SelectionValue } from '../sushi.types'; /** One option displayed by a select button. */ export type SelectButtonOption = SelectionOption; diff --git a/sushi/src/lib/select-button/select-button.templates.ts b/sushi/src/lib/select-button/select-button.templates.ts index c707093a..4d122768 100644 --- a/sushi/src/lib/select-button/select-button.templates.ts +++ b/sushi/src/lib/select-button/select-button.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { SelectButtonOptionContext } from './select-button.interfaces'; +import { type SelectButtonOptionContext } from './select-button.interfaces'; /** Replaces every option label and exposes option, index, selected, and disabled state. */ @Directive({ diff --git a/sushi/src/lib/select-button/testing/select-button.component.spec.ts b/sushi/src/lib/select-button/testing/select-button.component.spec.ts index 83a0f315..0b0d41b3 100644 --- a/sushi/src/lib/select-button/testing/select-button.component.spec.ts +++ b/sushi/src/lib/select-button/testing/select-button.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { press, query, queryAll, render } from '../../../../testing/test-utils'; +import { press, query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { SelectButton } from '../select-button.component'; import type { SelectButtonOption, SelectButtonOrientation, SelectButtonValue } from '../select-button.interfaces'; import { SelectButtonOptionTemplate } from '../select-button.templates'; @@ -50,8 +50,8 @@ describe('SelectButton semantics', (): void => { expect(group.getAttribute('aria-label')).toBe('View'); expect(group.getAttribute('aria-orientation')).toBe('horizontal'); expect(radios.map((radio: Element): string | null => radio.getAttribute('tabindex'))).toEqual(['0', '-1', '-1']); - expect(radios[0].getAttribute('aria-checked')).toBe('true'); - expect(radios[2].getAttribute('aria-disabled')).toBe('true'); + expect(requireItem(radios, 0).getAttribute('aria-checked')).toBe('true'); + expect(requireItem(radios, 2).getAttribute('aria-disabled')).toBe('true'); }); it('exposes option state and index to a custom template', (): void => { @@ -77,14 +77,14 @@ describe('SelectButton interaction', (): void => { it('selects an enabled option and optionally clears it', (): void => { const fixture: ComponentFixture = render(SelectButtonHost); const radios: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - radios[1].click(); + requireItem(radios, 1).click(); fixture.detectChanges(); expect(fixture.componentInstance.value()).toBe('board'); fixture.componentInstance.allowEmpty.set(true); fixture.detectChanges(); - radios[1].click(); + requireItem(radios, 1).click(); expect(fixture.componentInstance.value()).toBeNull(); expect(fixture.componentInstance.touches).toBe(2); @@ -93,27 +93,27 @@ describe('SelectButton interaction', (): void => { it('moves and selects with orientation-aware arrow keys', (): void => { const fixture: ComponentFixture = render(SelectButtonHost); const radios: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - press(radios[0], 'ArrowRight'); + press(requireItem(radios, 0), 'ArrowRight'); fixture.detectChanges(); - expect(document.activeElement).toBe(radios[1]); + expect(document.activeElement).toBe(requireItem(radios, 1)); expect(fixture.componentInstance.value()).toBe('board'); fixture.componentInstance.orientation.set('vertical'); fixture.detectChanges(); - press(radios[1], 'ArrowUp'); + press(requireItem(radios, 1), 'ArrowUp'); - expect(document.activeElement).toBe(radios[0]); + expect(document.activeElement).toBe(requireItem(radios, 0)); expect(fixture.componentInstance.value()).toBe('list'); }); it('keeps disabled options focusable without changing the value', (): void => { const fixture: ComponentFixture = render(SelectButtonHost); const radios: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - radios[2].focus(); - radios[2].click(); + requireItem(radios, 2).focus(); + requireItem(radios, 2).click(); - expect(document.activeElement).toBe(radios[2]); + expect(document.activeElement).toBe(requireItem(radios, 2)); expect(fixture.componentInstance.value()).toBe('list'); }); }); diff --git a/sushi/src/lib/select/index.ts b/sushi/src/lib/select/index.ts index ad162feb..394962da 100644 --- a/sushi/src/lib/select/index.ts +++ b/sushi/src/lib/select/index.ts @@ -1,3 +1,3 @@ export * from './select.component'; -export * from './select.interfaces'; +export type * from './select.interfaces'; export * from './select.templates'; diff --git a/sushi/src/lib/select/select.component.ts b/sushi/src/lib/select/select.component.ts index 150893f8..a17cd011 100644 --- a/sushi/src/lib/select/select.component.ts +++ b/sushi/src/lib/select/select.component.ts @@ -6,27 +6,27 @@ import { Component, computed, contentChild, - ElementRef, + type ElementRef, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, numberAttribute, output, - OutputEmitterRef, + type OutputEmitterRef, signal, - Signal, + type Signal, TemplateRef, viewChild, - WritableSignal, + type WritableSignal, } from '@angular/core'; -import { FormValueControl } from '@angular/forms/signals'; +import { type FormValueControl } from '@angular/forms/signals'; import { Combobox, ComboboxPopup, ComboboxWidget } from '@angular/aria/combobox'; import { Listbox, Option } from '@angular/aria/listbox'; -import { CdkConnectedOverlay, CdkOverlayOrigin, ConnectedPosition } from '@angular/cdk/overlay'; +import { CdkConnectedOverlay, CdkOverlayOrigin, type ConnectedPosition } from '@angular/cdk/overlay'; import { LucideCheck, LucideChevronDown, LucideX } from '@lucide/angular'; -import { FormControlSeverity, FormControlSize, FormControlState } from '../form-control'; +import { type FormControlSeverity, type FormControlSize, FormControlState } from '../form-control'; import { compareSelectionValues, filterSelectionOption, @@ -36,15 +36,15 @@ import { } from '../selection'; import { Spinner } from '../spinner'; import { - SelectCompareWith, - SelectGroupContext, - SelectItemContext, - SelectLoadingContext, - SelectModelValue, - SelectOption, - SelectSelectedItemContext, - SelectValue, - SelectVariant, + type SelectCompareWith, + type SelectGroupContext, + type SelectItemContext, + type SelectLoadingContext, + type SelectModelValue, + type SelectOption, + type SelectSelectedItemContext, + type SelectValue, + type SelectVariant, } from './select.interfaces'; import { SelectClearIconTemplate, diff --git a/sushi/src/lib/select/select.interfaces.ts b/sushi/src/lib/select/select.interfaces.ts index 06d99682..ac1fd242 100644 --- a/sushi/src/lib/select/select.interfaces.ts +++ b/sushi/src/lib/select/select.interfaces.ts @@ -1,5 +1,5 @@ -import { SelectionCompareWith, SelectionOption } from '../selection'; -import { SelectionValue } from '../sushi.types'; +import { type SelectionCompareWith, type SelectionOption } from '../selection'; +import { type SelectionValue } from '../sushi.types'; /** Value accepted by a select option. */ export type SelectValue = SelectionValue; diff --git a/sushi/src/lib/select/select.templates.ts b/sushi/src/lib/select/select.templates.ts index df5b2016..816a1ac7 100644 --- a/sushi/src/lib/select/select.templates.ts +++ b/sushi/src/lib/select/select.templates.ts @@ -1,5 +1,10 @@ import { Directive } from '@angular/core'; -import { SelectGroupContext, SelectItemContext, SelectLoadingContext, SelectSelectedItemContext } from './select.interfaces'; +import { + type SelectGroupContext, + type SelectItemContext, + type SelectLoadingContext, + type SelectSelectedItemContext, +} from './select.interfaces'; /** Replaces each option and exposes its index, selected, and disabled state. */ @Directive({ selector: 'ng-template[suiSelectItem]' }) diff --git a/sushi/src/lib/select/testing/select-filter.component.spec.ts b/sushi/src/lib/select/testing/select-filter.component.spec.ts index 0890b280..66559c22 100644 --- a/sushi/src/lib/select/testing/select-filter.component.spec.ts +++ b/sushi/src/lib/select/testing/select-filter.component.spec.ts @@ -1,9 +1,9 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Select } from '../select.component'; -import { SelectModelValue, SelectOption } from '../select.interfaces'; +import { type SelectModelValue, type SelectOption } from '../select.interfaces'; @Component({ imports: [Select], diff --git a/sushi/src/lib/select/testing/select.component.spec.ts b/sushi/src/lib/select/testing/select.component.spec.ts index 275b3c64..7a55969f 100644 --- a/sushi/src/lib/select/testing/select.component.spec.ts +++ b/sushi/src/lib/select/testing/select.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, Signal, viewChild, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type Signal, viewChild, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, describe, expect, it } from 'vitest'; -import { query, render } from '../../../../testing/test-utils'; +import { query, render, requireItem } from '../../../../testing/test-utils'; import { Select } from '../select.component'; import type { SelectModelValue, SelectOption } from '../select.interfaces'; import { @@ -190,9 +190,9 @@ describe('Select option selection', (): void => { const items: NodeListOf = document.querySelectorAll('[role="option"]'); expect(items).toHaveLength(3); - expect(items[2].getAttribute('aria-disabled')).toBe('true'); + expect(requireItem(items, 2).getAttribute('aria-disabled')).toBe('true'); - items[0].click(); + requireItem(items, 0).click(); fixture.detectChanges(); expect(fixture.componentInstance.value()).toBe('miso'); @@ -204,7 +204,7 @@ describe('Select option selection', (): void => { query(fixture, '[role="combobox"]').dispatchEvent(new MouseEvent('click', { bubbles: true })); await fixture.whenStable(); - document.querySelectorAll('[role="option"]')[2].click(); + requireItem(document.querySelectorAll('[role="option"]'), 2).click(); fixture.detectChanges(); expect(fixture.componentInstance.value()).toBe('shoyu'); diff --git a/sushi/src/lib/selection/index.ts b/sushi/src/lib/selection/index.ts index 61aa09be..62725d4d 100644 --- a/sushi/src/lib/selection/index.ts +++ b/sushi/src/lib/selection/index.ts @@ -1,3 +1,3 @@ -export * from './selection.interfaces'; +export type * from './selection.interfaces'; export * from './selection-overlay'; export * from './selection.utils'; diff --git a/sushi/src/lib/selection/selection-overlay.ts b/sushi/src/lib/selection/selection-overlay.ts index 32310888..c46f070e 100644 --- a/sushi/src/lib/selection/selection-overlay.ts +++ b/sushi/src/lib/selection/selection-overlay.ts @@ -1,4 +1,4 @@ -import { ConnectedPosition } from '@angular/cdk/overlay'; +import { type ConnectedPosition } from '@angular/cdk/overlay'; export const SELECTION_BELOW: ConnectedPosition = { originX: 'start', diff --git a/sushi/src/lib/selection/selection.interfaces.ts b/sushi/src/lib/selection/selection.interfaces.ts index dbb6fe1b..602af9d8 100644 --- a/sushi/src/lib/selection/selection.interfaces.ts +++ b/sushi/src/lib/selection/selection.interfaces.ts @@ -1,4 +1,4 @@ -import { SelectionValue } from '../sushi.types'; +import { type SelectionValue } from '../sushi.types'; /** One selectable label and value with optional disabled state. */ export interface SelectionOption { diff --git a/sushi/src/lib/selection/selection.utils.ts b/sushi/src/lib/selection/selection.utils.ts index 788e41d8..05c5c978 100644 --- a/sushi/src/lib/selection/selection.utils.ts +++ b/sushi/src/lib/selection/selection.utils.ts @@ -1,5 +1,5 @@ -import { SelectionValue } from '../sushi.types'; -import { SelectionCompareWith, SelectionFilter, SelectionOption } from './selection.interfaces'; +import { type SelectionValue } from '../sushi.types'; +import { type SelectionCompareWith, type SelectionFilter, type SelectionOption } from './selection.interfaces'; export const compareSelectionValues: SelectionCompareWith = (first: SelectionValue, second: SelectionValue): boolean => Object.is(first, second); diff --git a/sushi/src/lib/sidebar/index.ts b/sushi/src/lib/sidebar/index.ts index 5b880877..90987379 100644 --- a/sushi/src/lib/sidebar/index.ts +++ b/sushi/src/lib/sidebar/index.ts @@ -1,5 +1,5 @@ export * from './sidebar.component'; export * from './sidebar-group-template.directive'; -export * from './sidebar.interfaces'; +export type * from './sidebar.interfaces'; export * from './sidebar-item-template.directive'; export * from './sidebar-slots.directive'; diff --git a/sushi/src/lib/sidebar/sidebar-group-template.directive.ts b/sushi/src/lib/sidebar/sidebar-group-template.directive.ts index 9110d079..3fbeb9c2 100644 --- a/sushi/src/lib/sidebar/sidebar-group-template.directive.ts +++ b/sushi/src/lib/sidebar/sidebar-group-template.directive.ts @@ -1,6 +1,6 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { NavbarItem } from '../navbar'; -import { SidebarGroup, SidebarGroupContext } from './sidebar.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type NavbarItem } from '../navbar'; +import { type SidebarGroup, type SidebarGroupContext } from './sidebar.interfaces'; /** Customizes visible Sidebar group headings without changing navigation items. */ @Directive({ selector: 'ng-template[suiSidebarGroup]' }) diff --git a/sushi/src/lib/sidebar/sidebar-item-template.directive.ts b/sushi/src/lib/sidebar/sidebar-item-template.directive.ts index 30e86023..4f3c22c1 100644 --- a/sushi/src/lib/sidebar/sidebar-item-template.directive.ts +++ b/sushi/src/lib/sidebar/sidebar-item-template.directive.ts @@ -1,6 +1,6 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { NavbarItem } from '../navbar'; -import { SidebarGroup, SidebarItemContext } from './sidebar.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type NavbarItem } from '../navbar'; +import { type SidebarGroup, type SidebarItemContext } from './sidebar.interfaces'; /** Customizes each destination rendered from Sidebar groups. */ @Directive({ selector: 'ng-template[suiSidebarItem]' }) diff --git a/sushi/src/lib/sidebar/sidebar.component.ts b/sushi/src/lib/sidebar/sidebar.component.ts index 9662f2b1..aaed5d87 100644 --- a/sushi/src/lib/sidebar/sidebar.component.ts +++ b/sushi/src/lib/sidebar/sidebar.component.ts @@ -6,17 +6,17 @@ import { computed, contentChild, input, - InputSignal, + type InputSignal, model, - ModelSignal, - Signal, + type ModelSignal, + type Signal, TemplateRef, } from '@angular/core'; -import { Navbar, NavbarItem, NavbarItemTemplate } from '../navbar'; +import { Navbar, type NavbarItem, NavbarItemTemplate } from '../navbar'; import { Menu, MenuItemTemplate, MenuTrigger } from '../menu'; import { SidebarGroupTemplate } from './sidebar-group-template.directive'; import { SidebarItemTemplate } from './sidebar-item-template.directive'; -import { SidebarGroup, SidebarGroupContext, SidebarItemContext, SidebarSize } from './sidebar.interfaces'; +import { type SidebarGroup, type SidebarGroupContext, type SidebarItemContext, type SidebarSize } from './sidebar.interfaces'; import { SidebarFooter, SidebarHeader } from './sidebar-slots.directive'; /** Renders persistent, grouped application navigation with optional header and footer regions. */ diff --git a/sushi/src/lib/sidebar/sidebar.interfaces.ts b/sushi/src/lib/sidebar/sidebar.interfaces.ts index 6db16301..2c941b21 100644 --- a/sushi/src/lib/sidebar/sidebar.interfaces.ts +++ b/sushi/src/lib/sidebar/sidebar.interfaces.ts @@ -1,4 +1,4 @@ -import { NavbarItem, NavbarItemContext } from '../navbar'; +import { type NavbarItem, type NavbarItemContext } from '../navbar'; /** Supported persistent Sidebar widths. */ export type SidebarSize = 'sm' | 'md' | 'lg'; diff --git a/sushi/src/lib/sidebar/testing/sidebar.component.spec.ts b/sushi/src/lib/sidebar/testing/sidebar.component.spec.ts index 53ad6485..6e2272fb 100644 --- a/sushi/src/lib/sidebar/testing/sidebar.component.spec.ts +++ b/sushi/src/lib/sidebar/testing/sidebar.component.spec.ts @@ -1,12 +1,12 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, describe, expect, it } from 'vitest'; import { query, queryAll, render } from '../../../../testing/test-utils'; -import { NavbarItem } from '../../navbar'; +import { type NavbarItem } from '../../navbar'; import { Sidebar } from '../sidebar.component'; import { SidebarGroupTemplate } from '../sidebar-group-template.directive'; import { SidebarItemTemplate } from '../sidebar-item-template.directive'; -import { SidebarGroup } from '../sidebar.interfaces'; +import { type SidebarGroup } from '../sidebar.interfaces'; import { SidebarFooter, SidebarHeader } from '../sidebar-slots.directive'; type Page = 'overview' | 'activity' | 'reports' | 'activity-reports' | 'settings'; diff --git a/sushi/src/lib/skeleton/index.ts b/sushi/src/lib/skeleton/index.ts index 509c5fb5..fe9d566b 100644 --- a/sushi/src/lib/skeleton/index.ts +++ b/sushi/src/lib/skeleton/index.ts @@ -1,2 +1,2 @@ export * from './skeleton.directive'; -export * from './skeleton.interfaces'; +export type * from './skeleton.interfaces'; diff --git a/sushi/src/lib/skeleton/skeleton.directive.ts b/sushi/src/lib/skeleton/skeleton.directive.ts index a9a89021..3fb3a1c6 100644 --- a/sushi/src/lib/skeleton/skeleton.directive.ts +++ b/sushi/src/lib/skeleton/skeleton.directive.ts @@ -1,5 +1,5 @@ -import { booleanAttribute, Directive, input, InputSignal, InputSignalWithTransform } from '@angular/core'; -import { SkeletonVariant } from './skeleton.interfaces'; +import { booleanAttribute, Directive, input, type InputSignal, type InputSignalWithTransform } from '@angular/core'; +import { type SkeletonVariant } from './skeleton.interfaces'; /** Marks a non-interactive placeholder for content that is still loading. */ @Directive({ diff --git a/sushi/src/lib/skeleton/testing/skeleton.directive.spec.ts b/sushi/src/lib/skeleton/testing/skeleton.directive.spec.ts index 83b76731..d766a45b 100644 --- a/sushi/src/lib/skeleton/testing/skeleton.directive.spec.ts +++ b/sushi/src/lib/skeleton/testing/skeleton.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Skeleton } from '../skeleton.directive'; diff --git a/sushi/src/lib/spinner/index.ts b/sushi/src/lib/spinner/index.ts index 2edd1262..5bfe2d1e 100644 --- a/sushi/src/lib/spinner/index.ts +++ b/sushi/src/lib/spinner/index.ts @@ -1,2 +1,2 @@ export * from './spinner.directive'; -export * from './spinner.interfaces'; +export type * from './spinner.interfaces'; diff --git a/sushi/src/lib/spinner/spinner.directive.ts b/sushi/src/lib/spinner/spinner.directive.ts index 19be3246..40e33e97 100644 --- a/sushi/src/lib/spinner/spinner.directive.ts +++ b/sushi/src/lib/spinner/spinner.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { SpinnerSize, SpinnerType } from './spinner.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type SpinnerSize, type SpinnerType } from './spinner.interfaces'; @Directive({ selector: 'span[suiSpinner]', diff --git a/sushi/src/lib/spinner/spinner.interfaces.ts b/sushi/src/lib/spinner/spinner.interfaces.ts index d696cc4b..61603aae 100644 --- a/sushi/src/lib/spinner/spinner.interfaces.ts +++ b/sushi/src/lib/spinner/spinner.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize } from '../sushi.types'; +import { type ComponentSize } from '../sushi.types'; /** Built-in loading animation shapes. */ export type SpinnerType = 'spinner' | 'dots' | 'ring' | 'ball' | 'bars' | 'infinity'; diff --git a/sushi/src/lib/spinner/testing/spinner.directive.spec.ts b/sushi/src/lib/spinner/testing/spinner.directive.spec.ts index 7471d69c..da2f4ac5 100644 --- a/sushi/src/lib/spinner/testing/spinner.directive.spec.ts +++ b/sushi/src/lib/spinner/testing/spinner.directive.spec.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Spinner } from '../spinner.directive'; diff --git a/sushi/src/lib/status/index.ts b/sushi/src/lib/status/index.ts index f68c8e3a..1a8f595c 100644 --- a/sushi/src/lib/status/index.ts +++ b/sushi/src/lib/status/index.ts @@ -1,2 +1,2 @@ export * from './status.directive'; -export * from './status.interfaces'; +export type * from './status.interfaces'; diff --git a/sushi/src/lib/status/status.directive.ts b/sushi/src/lib/status/status.directive.ts index 59d5da94..8c058f73 100644 --- a/sushi/src/lib/status/status.directive.ts +++ b/sushi/src/lib/status/status.directive.ts @@ -1,5 +1,5 @@ -import { Directive, input, InputSignal } from '@angular/core'; -import { StatusAnimation, StatusSeverity, StatusSize } from './status.interfaces'; +import { Directive, input, type InputSignal } from '@angular/core'; +import { type StatusAnimation, type StatusSeverity, type StatusSize } from './status.interfaces'; @Directive({ selector: 'span[suiStatus]', diff --git a/sushi/src/lib/status/status.interfaces.ts b/sushi/src/lib/status/status.interfaces.ts index 1c21c7e8..98df621b 100644 --- a/sushi/src/lib/status/status.interfaces.ts +++ b/sushi/src/lib/status/status.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize, ThemeSeverity } from '../sushi.types'; +import { type ComponentSize, type ThemeSeverity } from '../sushi.types'; /** Semantic colors available to status markers. */ export type StatusSeverity = ThemeSeverity; diff --git a/sushi/src/lib/status/testing/status.directive.spec.ts b/sushi/src/lib/status/testing/status.directive.spec.ts index 5cfea9f1..c674e54b 100644 --- a/sushi/src/lib/status/testing/status.directive.spec.ts +++ b/sushi/src/lib/status/testing/status.directive.spec.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Status } from '../status.directive'; diff --git a/sushi/src/lib/table/index.ts b/sushi/src/lib/table/index.ts index 0d98b2ad..385b7f1c 100644 --- a/sushi/src/lib/table/index.ts +++ b/sushi/src/lib/table/index.ts @@ -1,3 +1,3 @@ export * from './table.component'; -export * from './table.interfaces'; +export type * from './table.interfaces'; export * from './table.templates'; diff --git a/sushi/src/lib/table/table.component.ts b/sushi/src/lib/table/table.component.ts index 0b4934d8..a4ae612b 100644 --- a/sushi/src/lib/table/table.component.ts +++ b/sushi/src/lib/table/table.component.ts @@ -6,27 +6,27 @@ import { computed, contentChild, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, numberAttribute, - Signal, + type Signal, TemplateRef, } from '@angular/core'; import { LucideArrowDown, LucideArrowUp, LucideArrowUpDown } from '@lucide/angular'; import { Button } from '../button'; import { Spinner } from '../spinner'; import { - TableCellContext, - TableCollectionContext, - TableColumn, - TableHeaderContext, - TableRowContext, - TableSize, - TableSort, - TableSortDirection, - TableTrackBy, + type TableCellContext, + type TableCollectionContext, + type TableColumn, + type TableHeaderContext, + type TableRowContext, + type TableSize, + type TableSort, + type TableSortDirection, + type TableTrackBy, } from './table.interfaces'; import { TableCaptionTemplate, diff --git a/sushi/src/lib/table/table.interfaces.ts b/sushi/src/lib/table/table.interfaces.ts index 674967b2..20258a50 100644 --- a/sushi/src/lib/table/table.interfaces.ts +++ b/sushi/src/lib/table/table.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize } from '../sushi.types'; +import { type ComponentSize } from '../sushi.types'; /** Size scale available to table cells. */ export type TableSize = ComponentSize; diff --git a/sushi/src/lib/table/table.templates.ts b/sushi/src/lib/table/table.templates.ts index 0c9653ed..302d4510 100644 --- a/sushi/src/lib/table/table.templates.ts +++ b/sushi/src/lib/table/table.templates.ts @@ -1,5 +1,10 @@ import { Directive } from '@angular/core'; -import { TableCellContext, TableCollectionContext, TableHeaderContext, TableRowContext } from './table.interfaces'; +import { + type TableCellContext, + type TableCollectionContext, + type TableHeaderContext, + type TableRowContext, +} from './table.interfaces'; /** Replaces the native caption and exposes the rendered collection. */ @Directive({ selector: 'ng-template[suiTableCaption]' }) diff --git a/sushi/src/lib/table/testing/table.component.spec.ts b/sushi/src/lib/table/testing/table.component.spec.ts index b7c6ee0d..187981e0 100644 --- a/sushi/src/lib/table/testing/table.component.spec.ts +++ b/sushi/src/lib/table/testing/table.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { query, queryAll, render } from '../../../../testing/test-utils'; +import { query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { Table } from '../table.component'; import type { TableColumn, TableSort } from '../table.interfaces'; import { @@ -110,8 +110,8 @@ describe('Table native structure and templates', (): void => { const fixture: ComponentFixture = render(TableHost); const headings: readonly HTMLTableCellElement[] = queryAll(fixture, 'thead th') as readonly HTMLTableCellElement[]; - expect(headings[0].getAttribute('aria-sort')).toBe('none'); - expect(headings[1].classList.contains('sui-table__cell--end')).toBe(true); + expect(requireItem(headings, 0).getAttribute('aria-sort')).toBe('none'); + expect(requireItem(headings, 1).classList.contains('sui-table__cell--end')).toBe(true); expect(query(fixture, '[data-header="name"]').getAttribute('data-direction')).toBeNull(); }); @@ -135,12 +135,12 @@ describe('Table native structure and templates', (): void => { describe('Table controlled sorting', (): void => { it('cycles ascending, descending, and unsorted states', (): void => { const fixture: ComponentFixture = render(TableHost); - const sortName: HTMLButtonElement = queryAll(fixture, 'button')[0]; + const sortName: HTMLButtonElement = requireItem(queryAll(fixture, 'button'), 0); sortName.click(); fixture.detectChanges(); expect(fixture.componentInstance.sort()).toEqual({ key: 'name', direction: 'ascending' }); - expect(queryAll(fixture, 'thead th')[0].getAttribute('aria-sort')).toBe('ascending'); + expect(requireItem(queryAll(fixture, 'thead th'), 0).getAttribute('aria-sort')).toBe('ascending'); sortName.click(); fixture.detectChanges(); @@ -155,7 +155,7 @@ describe('Table controlled sorting', (): void => { it('switches sorting to another column', (): void => { const fixture: ComponentFixture = render(TableHost); - const sortLength: HTMLButtonElement = queryAll(fixture, 'button')[1]; + const sortLength: HTMLButtonElement = requireItem(queryAll(fixture, 'button'), 1); fixture.componentInstance.sort.set({ key: 'name', direction: 'descending' }); fixture.detectChanges(); sortLength.click(); @@ -176,7 +176,7 @@ describe('Table collection states', (): void => { it('renders loading state and blocks sorting without losing focus', (): void => { const fixture: ComponentFixture = render(TableHost); - const sortName: HTMLButtonElement = queryAll(fixture, 'button')[0]; + const sortName: HTMLButtonElement = requireItem(queryAll(fixture, 'button'), 0); fixture.componentInstance.loading.set(true); fixture.detectChanges(); sortName.focus(); diff --git a/sushi/src/lib/tabs/index.ts b/sushi/src/lib/tabs/index.ts index 301e6929..a7006b4e 100644 --- a/sushi/src/lib/tabs/index.ts +++ b/sushi/src/lib/tabs/index.ts @@ -1,3 +1,3 @@ export * from './tab.directive'; export * from './tabs.component'; -export * from './tabs.interfaces'; +export type * from './tabs.interfaces'; diff --git a/sushi/src/lib/tabs/tab.directive.ts b/sushi/src/lib/tabs/tab.directive.ts index dc59a7ce..277fbd64 100644 --- a/sushi/src/lib/tabs/tab.directive.ts +++ b/sushi/src/lib/tabs/tab.directive.ts @@ -1,5 +1,13 @@ -import { booleanAttribute, Directive, inject, input, InputSignal, InputSignalWithTransform, TemplateRef } from '@angular/core'; -import { TabsValue } from './tabs.interfaces'; +import { + booleanAttribute, + Directive, + inject, + input, + type InputSignal, + type InputSignalWithTransform, + TemplateRef, +} from '@angular/core'; +import { type TabsValue } from './tabs.interfaces'; /** Defines one tab label, value, and panel template. */ @Directive({ selector: 'ng-template[suiTab]' }) diff --git a/sushi/src/lib/tabs/tabs.component.css b/sushi/src/lib/tabs/tabs.component.css index 096b1580..aeefb8c5 100644 --- a/sushi/src/lib/tabs/tabs.component.css +++ b/sushi/src/lib/tabs/tabs.component.css @@ -22,7 +22,7 @@ background-color: var(--sui-tabs-panel-background); } -.sui-tab[aria-disabled='true'] { +.sui-tab[aria-disabled="true"] { cursor: not-allowed; opacity: var(--sui-tabs-disabled-opacity); pointer-events: auto; diff --git a/sushi/src/lib/tabs/tabs.component.ts b/sushi/src/lib/tabs/tabs.component.ts index b99f8f59..900d9979 100644 --- a/sushi/src/lib/tabs/tabs.component.ts +++ b/sushi/src/lib/tabs/tabs.component.ts @@ -12,14 +12,21 @@ import { Component, contentChildren, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, - Signal, + type ModelSignal, + type Signal, } from '@angular/core'; import { Tab } from './tab.directive'; -import { TabsOrientation, TabsPlacement, TabsSelectionMode, TabsSize, TabsValue, TabsVariant } from './tabs.interfaces'; +import { + type TabsOrientation, + type TabsPlacement, + type TabsSelectionMode, + type TabsSize, + type TabsValue, + type TabsVariant, +} from './tabs.interfaces'; @Component({ selector: 'sui-tabs', diff --git a/sushi/src/lib/tabs/tabs.interfaces.ts b/sushi/src/lib/tabs/tabs.interfaces.ts index 9574d92b..a7929091 100644 --- a/sushi/src/lib/tabs/tabs.interfaces.ts +++ b/sushi/src/lib/tabs/tabs.interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentSize, Orientation } from '../sushi.types'; +import { type ComponentSize, type Orientation } from '../sushi.types'; /** Identifier used to select one tab. */ export type TabsValue = string; diff --git a/sushi/src/lib/tabs/testing/tabs.component.spec.ts b/sushi/src/lib/tabs/testing/tabs.component.spec.ts index 0240eee8..18eda162 100644 --- a/sushi/src/lib/tabs/testing/tabs.component.spec.ts +++ b/sushi/src/lib/tabs/testing/tabs.component.spec.ts @@ -1,7 +1,7 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; -import { press, query, queryAll, render } from '../../../../testing/test-utils'; +import { press, query, queryAll, render, requireItem } from '../../../../testing/test-utils'; import { Tab } from '../tab.directive'; import { Tabs } from '../tabs.component'; import type { TabsOrientation, TabsSelectionMode, TabsValue } from '../tabs.interfaces'; @@ -45,16 +45,16 @@ describe('Tabs semantics and model', (): void => { expect(list.getAttribute('aria-label')).toBe('Recipe sections'); expect(list.getAttribute('aria-orientation')).toBe('horizontal'); expect(tabs).toHaveLength(3); - expect(tabs[0].getAttribute('aria-selected')).toBe('true'); - expect(tabs[2].getAttribute('aria-disabled')).toBe('true'); + expect(requireItem(tabs, 0).getAttribute('aria-selected')).toBe('true'); + expect(requireItem(tabs, 2).getAttribute('aria-disabled')).toBe('true'); const panels: readonly Element[] = queryAll(fixture, '[role="tabpanel"]'); expect(panels).toHaveLength(3); tabs.forEach((tab: Element, index: number): void => { - expect(tab.getAttribute('aria-controls')).toBe(panels[index].id); - expect(panels[index].getAttribute('aria-labelledby')).toBe(tab.id); + expect(tab.getAttribute('aria-controls')).toBe(requireItem(panels, index).id); + expect(requireItem(panels, index).getAttribute('aria-labelledby')).toBe(tab.id); }); expect(query(fixture, '[data-panel="overview"]').textContent).toBe('Summary'); @@ -63,11 +63,11 @@ describe('Tabs semantics and model', (): void => { it('activates an enabled tab through pointer selection', (): void => { const fixture: ComponentFixture = render(TabsHost); const tabs: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - tabs[1].click(); + requireItem(tabs, 1).click(); fixture.detectChanges(); expect(fixture.componentInstance.value()).toBe('steps'); - expect(tabs[1].getAttribute('aria-selected')).toBe('true'); + expect(requireItem(tabs, 1).getAttribute('aria-selected')).toBe('true'); expect(query(fixture, '[data-panel="steps"]').textContent).toBe('Instructions'); }); @@ -87,12 +87,12 @@ describe('Tabs panel lifecycle', (): void => { const fixture: ComponentFixture = render(TabsHost); const host: HTMLElement = fixture.nativeElement as HTMLElement; const tabs: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - tabs[1].click(); + requireItem(tabs, 1).click(); fixture.detectChanges(); expect(host.querySelector('[data-panel="overview"]')).toBeNull(); - tabs[0].click(); + requireItem(tabs, 0).click(); fixture.detectChanges(); expect(host.querySelector('[data-panel="steps"]')).not.toBeNull(); @@ -103,11 +103,11 @@ describe('Tabs keyboard behavior', (): void => { it('follows horizontal arrow focus in follow mode', (): void => { const fixture: ComponentFixture = render(TabsHost); const tabs: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - tabs[0].focus(); - press(tabs[0], 'ArrowRight'); + requireItem(tabs, 0).focus(); + press(requireItem(tabs, 0), 'ArrowRight'); fixture.detectChanges(); - expect(document.activeElement).toBe(tabs[1]); + expect(document.activeElement).toBe(requireItem(tabs, 1)); expect(fixture.componentInstance.value()).toBe('steps'); }); @@ -116,11 +116,11 @@ describe('Tabs keyboard behavior', (): void => { const tabs: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); fixture.componentInstance.orientation.set('vertical'); fixture.detectChanges(); - tabs[0].focus(); - press(tabs[0], 'ArrowDown'); + requireItem(tabs, 0).focus(); + press(requireItem(tabs, 0), 'ArrowDown'); fixture.detectChanges(); - expect(document.activeElement).toBe(tabs[1]); + expect(document.activeElement).toBe(requireItem(tabs, 1)); expect(fixture.componentInstance.value()).toBe('steps'); }); @@ -129,12 +129,12 @@ describe('Tabs keyboard behavior', (): void => { const tabs: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); fixture.componentInstance.selectionMode.set('explicit'); fixture.detectChanges(); - tabs[0].focus(); - press(tabs[0], 'ArrowRight'); + requireItem(tabs, 0).focus(); + press(requireItem(tabs, 0), 'ArrowRight'); expect(fixture.componentInstance.value()).toBe('overview'); - press(tabs[1], 'Enter'); + press(requireItem(tabs, 1), 'Enter'); fixture.detectChanges(); expect(fixture.componentInstance.value()).toBe('steps'); @@ -145,12 +145,12 @@ describe('Tabs navigation boundaries', (): void => { it('skips disabled tabs during arrow navigation', (): void => { const fixture: ComponentFixture = render(TabsHost); const tabs: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - tabs[1].focus(); - press(tabs[1], 'ArrowRight'); + requireItem(tabs, 1).focus(); + press(requireItem(tabs, 1), 'ArrowRight'); fixture.detectChanges(); - expect(document.activeElement).not.toBe(tabs[2]); - expect(document.activeElement).toBe(tabs[1]); + expect(document.activeElement).not.toBe(requireItem(tabs, 2)); + expect(document.activeElement).toBe(requireItem(tabs, 1)); expect(fixture.componentInstance.value()).toBe('steps'); }); }); @@ -160,10 +160,10 @@ describe('Tabs disabled behavior', (): void => { const fixture: ComponentFixture = render(TabsHost); const tabs: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); - expect(tabs[2].getAttribute('aria-disabled')).toBe('true'); - expect(tabs[2].tabIndex).toBe(-1); + expect(requireItem(tabs, 2).getAttribute('aria-disabled')).toBe('true'); + expect(requireItem(tabs, 2).tabIndex).toBe(-1); - tabs[2].click(); + requireItem(tabs, 2).click(); expect(fixture.componentInstance.value()).toBe('overview'); }); @@ -173,10 +173,10 @@ describe('Tabs disabled behavior', (): void => { const tabs: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); fixture.componentInstance.softDisabled.set(true); fixture.detectChanges(); - tabs[2].focus(); - tabs[2].click(); + requireItem(tabs, 2).focus(); + requireItem(tabs, 2).click(); - expect(document.activeElement).toBe(tabs[2]); + expect(document.activeElement).toBe(requireItem(tabs, 2)); expect(fixture.componentInstance.value()).toBe('overview'); }); @@ -185,7 +185,7 @@ describe('Tabs disabled behavior', (): void => { const tabs: readonly HTMLButtonElement[] = queryAll(fixture, 'button'); fixture.componentInstance.disabled.set(true); fixture.detectChanges(); - tabs[1].click(); + requireItem(tabs, 1).click(); expect(fixture.componentInstance.value()).toBe('overview'); expect(query(fixture, '[role="tablist"]').getAttribute('aria-disabled')).toBe('true'); diff --git a/sushi/src/lib/textarea/index.ts b/sushi/src/lib/textarea/index.ts index 102aa723..c58a708d 100644 --- a/sushi/src/lib/textarea/index.ts +++ b/sushi/src/lib/textarea/index.ts @@ -1,2 +1,2 @@ export * from './textarea.directive'; -export * from './textarea.interfaces'; +export type * from './textarea.interfaces'; diff --git a/sushi/src/lib/textarea/testing/textarea.directive.spec.ts b/sushi/src/lib/textarea/testing/textarea.directive.spec.ts index b13b0e4e..12c457e8 100644 --- a/sushi/src/lib/textarea/testing/textarea.directive.spec.ts +++ b/sushi/src/lib/textarea/testing/textarea.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Textarea } from '../textarea.directive'; diff --git a/sushi/src/lib/textarea/textarea.directive.ts b/sushi/src/lib/textarea/textarea.directive.ts index 67273269..1676e6c1 100644 --- a/sushi/src/lib/textarea/textarea.directive.ts +++ b/sushi/src/lib/textarea/textarea.directive.ts @@ -1,6 +1,6 @@ -import { afterNextRender, DestroyRef, Directive, ElementRef, inject, input, InputSignal } from '@angular/core'; +import { afterNextRender, DestroyRef, Directive, ElementRef, inject, input, type InputSignal } from '@angular/core'; import { FluidControlState } from '../form-control'; -import { TextareaResize } from './textarea.interfaces'; +import { type TextareaResize } from './textarea.interfaces'; /** Styles a native multiline text control and configures its resize behavior. */ @Directive({ @@ -48,6 +48,7 @@ export class Textarea extends FluidControlState { if (!parent) return; this.observer = new ResizeObserver(([entry]: ResizeObserverEntry[]): void => { + if (!entry) return; const inlineSize: number = entry.contentBoxSize[0]?.inlineSize ?? entry.contentRect.width; this.element.nativeElement.style.maxInlineSize = `${inlineSize}px`; }); diff --git a/sushi/src/lib/textarea/textarea.interfaces.ts b/sushi/src/lib/textarea/textarea.interfaces.ts index eb626366..e0b03675 100644 --- a/sushi/src/lib/textarea/textarea.interfaces.ts +++ b/sushi/src/lib/textarea/textarea.interfaces.ts @@ -1,4 +1,4 @@ -import { FormControlSeverity, FormControlSize } from '../form-control'; +import { type FormControlSeverity, type FormControlSize } from '../form-control'; /** Size scale available to textareas. */ export type TextareaSize = FormControlSize; diff --git a/sushi/src/lib/toast/testing/toast.component.spec.ts b/sushi/src/lib/toast/testing/toast.component.spec.ts index 8b011755..bcb910ac 100644 --- a/sushi/src/lib/toast/testing/toast.component.spec.ts +++ b/sushi/src/lib/toast/testing/toast.component.spec.ts @@ -1,10 +1,10 @@ -import { Component, inject, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, inject, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { query, queryAll, render } from '../../../../testing/test-utils'; import { Toast } from '../toast.component'; import { ToastService } from '../toast.service'; -import { ToastDismissLabel, ToastTemplateValue } from '../toast.interfaces'; +import { type ToastDismissLabel, type ToastTemplateValue } from '../toast.interfaces'; import { ToastContentTemplate } from '../toast.templates'; @Component({ diff --git a/sushi/src/lib/toast/toast.component.css b/sushi/src/lib/toast/toast.component.css index c4635afd..6a9e48ee 100644 --- a/sushi/src/lib/toast/toast.component.css +++ b/sushi/src/lib/toast/toast.component.css @@ -43,7 +43,7 @@ .sui-toast__item { animation: none; display: grid; - grid-template-areas: 'icon content close'; + grid-template-areas: "icon content close"; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; width: 100%; @@ -51,8 +51,8 @@ .sui-toast__item--with-actions { grid-template-areas: - 'icon content close' - '. actions .'; + "icon content close" + ". actions ."; row-gap: 0.5rem; } diff --git a/sushi/src/lib/toast/toast.component.ts b/sushi/src/lib/toast/toast.component.ts index 254643ab..589384a7 100644 --- a/sushi/src/lib/toast/toast.component.ts +++ b/sushi/src/lib/toast/toast.component.ts @@ -6,22 +6,22 @@ import { contentChildren, inject, input, - InputSignal, - InputSignalWithTransform, - Signal, + type InputSignal, + type InputSignalWithTransform, + type Signal, } from '@angular/core'; import { LucideCircleCheck, LucideCircleX, LucideDynamicIcon, LucideInfo, LucideTriangleAlert, LucideX } from '@lucide/angular'; import type { LucideIconData } from '@lucide/angular'; import { Button } from '../button'; -import { Message, MessageSeverity } from '../message'; +import { Message, type MessageSeverity } from '../message'; import { positiveInteger } from '../number.transforms'; import { - ToastDismissLabel, - ToastHorizontalPosition, - ToastItem, - ToastMotion, - ToastTemplateContext, - ToastVerticalPosition, + type ToastDismissLabel, + type ToastHorizontalPosition, + type ToastItem, + type ToastMotion, + type ToastTemplateContext, + type ToastVerticalPosition, } from './toast.interfaces'; import { ToastService } from './toast.service'; import { ToastContentTemplate } from './toast.templates'; diff --git a/sushi/src/lib/toast/toast.interfaces.ts b/sushi/src/lib/toast/toast.interfaces.ts index 493a04f1..5c90c1dc 100644 --- a/sushi/src/lib/toast/toast.interfaces.ts +++ b/sushi/src/lib/toast/toast.interfaces.ts @@ -1,4 +1,4 @@ -import { MessageSeverity, MessageVariant } from '../message'; +import { type MessageSeverity, type MessageVariant } from '../message'; /** Horizontal placement of a Toast outlet. */ export type ToastHorizontalPosition = 'start' | 'center' | 'end'; diff --git a/sushi/src/lib/toast/toast.service.ts b/sushi/src/lib/toast/toast.service.ts index 9249813c..6016e536 100644 --- a/sushi/src/lib/toast/toast.service.ts +++ b/sushi/src/lib/toast/toast.service.ts @@ -1,5 +1,5 @@ -import { DestroyRef, inject, Injectable, signal, Signal, WritableSignal } from '@angular/core'; -import { ToastItem, ToastOptions } from './toast.interfaces'; +import { DestroyRef, inject, Injectable, signal, type Signal, type WritableSignal } from '@angular/core'; +import { type ToastItem, type ToastOptions } from './toast.interfaces'; interface ToastTimer { pauses: number; @@ -17,7 +17,7 @@ export class ToastService { public readonly toasts: Signal; private readonly toastState: WritableSignal = signal([]); - private readonly timers: Map = new Map(); + private readonly timers: Map = new Map(); private readonly destroyRef: DestroyRef = inject(DestroyRef); public constructor() { @@ -38,6 +38,7 @@ export class ToastService { action: options.action ?? null, template: options.template ?? null, }; + this.toastState.update((items: readonly ToastItem[]): readonly ToastItem[] => [...items, item]); this.startTimer(item.id, item.duration); return item.id; @@ -63,6 +64,7 @@ export class ToastService { if (!timer) return; timer.pauses += 1; if (timer.pauses > 1 || !timer.timeout) return; + clearTimeout(timer.timeout); timer.timeout = null; timer.remaining = Math.max(0, timer.remaining - (Date.now() - timer.startedAt)); @@ -78,6 +80,7 @@ export class ToastService { this.dismiss(id); return; } + this.schedule(id, timer); } diff --git a/sushi/src/lib/toast/toast.templates.ts b/sushi/src/lib/toast/toast.templates.ts index f5c9b499..b18ade64 100644 --- a/sushi/src/lib/toast/toast.templates.ts +++ b/sushi/src/lib/toast/toast.templates.ts @@ -1,5 +1,5 @@ -import { Directive, inject, input, InputSignal, TemplateRef } from '@angular/core'; -import { ToastTemplateContext } from './toast.interfaces'; +import { Directive, inject, input, type InputSignal, TemplateRef } from '@angular/core'; +import { type ToastTemplateContext } from './toast.interfaces'; @Directive({ selector: 'ng-template[suiToastContent]' }) /** Registers named custom content that can be selected for individual Toasts. */ diff --git a/sushi/src/lib/toggle-button/index.ts b/sushi/src/lib/toggle-button/index.ts index f3809c6f..804ab5aa 100644 --- a/sushi/src/lib/toggle-button/index.ts +++ b/sushi/src/lib/toggle-button/index.ts @@ -1,3 +1,3 @@ export * from './toggle-button.component'; -export * from './toggle-button.interfaces'; +export type * from './toggle-button.interfaces'; export * from './toggle-button.templates'; diff --git a/sushi/src/lib/toggle-button/testing/toggle-button.component.spec.ts b/sushi/src/lib/toggle-button/testing/toggle-button.component.spec.ts index 518e6ee8..86d2110e 100644 --- a/sushi/src/lib/toggle-button/testing/toggle-button.component.spec.ts +++ b/sushi/src/lib/toggle-button/testing/toggle-button.component.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { ToggleButton } from '../toggle-button.component'; diff --git a/sushi/src/lib/toggle-button/toggle-button.component.ts b/sushi/src/lib/toggle-button/toggle-button.component.ts index 754d21ef..12d4666d 100644 --- a/sushi/src/lib/toggle-button/toggle-button.component.ts +++ b/sushi/src/lib/toggle-button/toggle-button.component.ts @@ -6,23 +6,23 @@ import { computed, contentChild, input, - InputSignal, - InputSignalWithTransform, + type InputSignal, + type InputSignalWithTransform, model, - ModelSignal, + type ModelSignal, output, - OutputEmitterRef, - Signal, + type OutputEmitterRef, + type Signal, TemplateRef, } from '@angular/core'; -import { FormCheckboxControl } from '@angular/forms/signals'; +import { type FormCheckboxControl } from '@angular/forms/signals'; import { Button } from '../button'; import { - ToggleButtonContext, - ToggleButtonSeverity, - ToggleButtonShape, - ToggleButtonSize, - ToggleButtonVariant, + type ToggleButtonContext, + type ToggleButtonSeverity, + type ToggleButtonShape, + type ToggleButtonSize, + type ToggleButtonVariant, } from './toggle-button.interfaces'; import { ToggleButtonOffTemplate, ToggleButtonOnTemplate, ToggleButtonTemplate } from './toggle-button.templates'; diff --git a/sushi/src/lib/toggle-button/toggle-button.interfaces.ts b/sushi/src/lib/toggle-button/toggle-button.interfaces.ts index 9589bb62..fddc32ac 100644 --- a/sushi/src/lib/toggle-button/toggle-button.interfaces.ts +++ b/sushi/src/lib/toggle-button/toggle-button.interfaces.ts @@ -1,4 +1,4 @@ -import { ButtonSeverity, ButtonShape, ButtonSize, ButtonVariant } from '../button'; +import { type ButtonSeverity, type ButtonShape, type ButtonSize, type ButtonVariant } from '../button'; /** Semantic colors available to toggle buttons. */ export type ToggleButtonSeverity = ButtonSeverity; diff --git a/sushi/src/lib/toggle-button/toggle-button.templates.ts b/sushi/src/lib/toggle-button/toggle-button.templates.ts index fba20d8e..9838caf0 100644 --- a/sushi/src/lib/toggle-button/toggle-button.templates.ts +++ b/sushi/src/lib/toggle-button/toggle-button.templates.ts @@ -1,5 +1,5 @@ import { Directive } from '@angular/core'; -import { ToggleButtonContext } from './toggle-button.interfaces'; +import { type ToggleButtonContext } from './toggle-button.interfaces'; /** Replaces the complete control while preserving the toggle state and handlers. */ @Directive({ diff --git a/sushi/src/lib/toggle/testing/toggle.directive.spec.ts b/sushi/src/lib/toggle/testing/toggle.directive.spec.ts index 61622a0b..7eedba88 100644 --- a/sushi/src/lib/toggle/testing/toggle.directive.spec.ts +++ b/sushi/src/lib/toggle/testing/toggle.directive.spec.ts @@ -1,5 +1,5 @@ -import { Component, signal, WritableSignal } from '@angular/core'; -import { ComponentFixture } from '@angular/core/testing'; +import { Component, signal, type WritableSignal } from '@angular/core'; +import { type ComponentFixture } from '@angular/core/testing'; import { describe, expect, it } from 'vitest'; import { query, render } from '../../../../testing/test-utils'; import { Toggle } from '../toggle.directive'; diff --git a/sushi/src/lib/tooltip/index.ts b/sushi/src/lib/tooltip/index.ts index 5c0b7a3f..b2538566 100644 --- a/sushi/src/lib/tooltip/index.ts +++ b/sushi/src/lib/tooltip/index.ts @@ -1,2 +1,2 @@ export * from './tooltip.directive'; -export * from './tooltip.interfaces'; +export type * from './tooltip.interfaces'; diff --git a/sushi/src/lib/tooltip/internal/tooltip-surface.component.ts b/sushi/src/lib/tooltip/internal/tooltip-surface.component.ts index b3780082..9d089c0e 100644 --- a/sushi/src/lib/tooltip/internal/tooltip-surface.component.ts +++ b/sushi/src/lib/tooltip/internal/tooltip-surface.component.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, Component, input, InputSignal } from '@angular/core'; -import { TooltipPlacement } from '../tooltip.interfaces'; +import { ChangeDetectionStrategy, Component, input, type InputSignal } from '@angular/core'; +import { type TooltipPlacement } from '../tooltip.interfaces'; @Component({ selector: 'sui-tooltip-surface', diff --git a/sushi/src/styles/features/dialog.styles.css b/sushi/src/styles/features/dialog.styles.css index f2b9d80d..2dfdd1b7 100644 --- a/sushi/src/styles/features/dialog.styles.css +++ b/sushi/src/styles/features/dialog.styles.css @@ -77,35 +77,35 @@ box-shadow: var(--sui-dialog-shadow); } - .sui-dialog[data-position='top'] { + .sui-dialog[data-position="top"] { margin: var(--sui-dialog-viewport-gap) auto auto; } - .sui-dialog[data-position='bottom'] { + .sui-dialog[data-position="bottom"] { margin: auto auto var(--sui-dialog-viewport-gap); } - .sui-dialog[data-position='left'] { + .sui-dialog[data-position="left"] { margin: auto auto auto var(--sui-dialog-viewport-gap); } - .sui-dialog[data-position='right'] { + .sui-dialog[data-position="right"] { margin: auto var(--sui-dialog-viewport-gap) auto auto; } - .sui-dialog[data-position='top-left'] { + .sui-dialog[data-position="top-left"] { margin: var(--sui-dialog-viewport-gap) auto auto var(--sui-dialog-viewport-gap); } - .sui-dialog[data-position='top-right'] { + .sui-dialog[data-position="top-right"] { margin: var(--sui-dialog-viewport-gap) var(--sui-dialog-viewport-gap) auto auto; } - .sui-dialog[data-position='bottom-left'] { + .sui-dialog[data-position="bottom-left"] { margin: auto auto var(--sui-dialog-viewport-gap) var(--sui-dialog-viewport-gap); } - .sui-dialog[data-position='bottom-right'] { + .sui-dialog[data-position="bottom-right"] { margin: auto var(--sui-dialog-viewport-gap) var(--sui-dialog-viewport-gap) auto; } @@ -139,47 +139,47 @@ min-width: 0; } - .sui-dialog[data-draggable='true'] > .sui-dialog-drag-region { + .sui-dialog[data-draggable="true"] > .sui-dialog-drag-region { cursor: move; touch-action: none; } - .sui-dialog-header[data-severity='primary'] { + .sui-dialog-header[data-severity="primary"] { --sui-dialog-header-background: var(--color-primary); --sui-dialog-header-color: var(--color-primary-content); } - .sui-dialog-header[data-severity='secondary'] { + .sui-dialog-header[data-severity="secondary"] { --sui-dialog-header-background: var(--color-secondary); --sui-dialog-header-color: var(--color-secondary-content); } - .sui-dialog-header[data-severity='accent'] { + .sui-dialog-header[data-severity="accent"] { --sui-dialog-header-background: var(--color-accent); --sui-dialog-header-color: var(--color-accent-content); } - .sui-dialog-header[data-severity='neutral'] { + .sui-dialog-header[data-severity="neutral"] { --sui-dialog-header-background: var(--color-neutral); --sui-dialog-header-color: var(--color-neutral-content); } - .sui-dialog-header[data-severity='info'] { + .sui-dialog-header[data-severity="info"] { --sui-dialog-header-background: var(--color-info); --sui-dialog-header-color: var(--color-info-content); } - .sui-dialog-header[data-severity='success'] { + .sui-dialog-header[data-severity="success"] { --sui-dialog-header-background: var(--color-success); --sui-dialog-header-color: var(--color-success-content); } - .sui-dialog-header[data-severity='warning'] { + .sui-dialog-header[data-severity="warning"] { --sui-dialog-header-background: var(--color-warning); --sui-dialog-header-color: var(--color-warning-content); } - .sui-dialog-header[data-severity='error'] { + .sui-dialog-header[data-severity="error"] { --sui-dialog-header-background: var(--color-error); --sui-dialog-header-color: var(--color-error-content); } @@ -199,35 +199,35 @@ gap: 0.25rem; } - .sui-dialog:has(.sui-dialog-header[data-severity='primary']) > .sui-dialog-header__actions { + .sui-dialog:has(.sui-dialog-header[data-severity="primary"]) > .sui-dialog-header__actions { color: var(--color-primary-content); } - .sui-dialog:has(.sui-dialog-header[data-severity='secondary']) > .sui-dialog-header__actions { + .sui-dialog:has(.sui-dialog-header[data-severity="secondary"]) > .sui-dialog-header__actions { color: var(--color-secondary-content); } - .sui-dialog:has(.sui-dialog-header[data-severity='accent']) > .sui-dialog-header__actions { + .sui-dialog:has(.sui-dialog-header[data-severity="accent"]) > .sui-dialog-header__actions { color: var(--color-accent-content); } - .sui-dialog:has(.sui-dialog-header[data-severity='neutral']) > .sui-dialog-header__actions { + .sui-dialog:has(.sui-dialog-header[data-severity="neutral"]) > .sui-dialog-header__actions { color: var(--color-neutral-content); } - .sui-dialog:has(.sui-dialog-header[data-severity='info']) > .sui-dialog-header__actions { + .sui-dialog:has(.sui-dialog-header[data-severity="info"]) > .sui-dialog-header__actions { color: var(--color-info-content); } - .sui-dialog:has(.sui-dialog-header[data-severity='success']) > .sui-dialog-header__actions { + .sui-dialog:has(.sui-dialog-header[data-severity="success"]) > .sui-dialog-header__actions { color: var(--color-success-content); } - .sui-dialog:has(.sui-dialog-header[data-severity='warning']) > .sui-dialog-header__actions { + .sui-dialog:has(.sui-dialog-header[data-severity="warning"]) > .sui-dialog-header__actions { color: var(--color-warning-content); } - .sui-dialog:has(.sui-dialog-header[data-severity='error']) > .sui-dialog-header__actions { + .sui-dialog:has(.sui-dialog-header[data-severity="error"]) > .sui-dialog-header__actions { color: var(--color-error-content); } @@ -305,21 +305,21 @@ } @layer components { - .sui-dialog[data-motion='fade'][open], - .sui-dialog[data-motion='fade'][open]::backdrop { + .sui-dialog[data-motion="fade"][open], + .sui-dialog[data-motion="fade"][open]::backdrop { animation: sui-dialog-enter var(--sui-dialog-enter-duration) var(--sui-dialog-motion-easing) both; } - .sui-dialog[data-motion='fade'][open][data-closing], - .sui-dialog[data-motion='fade'][open][data-closing]::backdrop { + .sui-dialog[data-motion="fade"][open][data-closing], + .sui-dialog[data-motion="fade"][open][data-closing]::backdrop { animation: sui-dialog-exit var(--sui-dialog-exit-duration) var(--sui-dialog-motion-easing) both; } @media (prefers-reduced-motion: reduce) { - .sui-dialog[data-motion='fade'][open], - .sui-dialog[data-motion='fade'][open]::backdrop, - .sui-dialog[data-motion='fade'][open][data-closing], - .sui-dialog[data-motion='fade'][open][data-closing]::backdrop { + .sui-dialog[data-motion="fade"][open], + .sui-dialog[data-motion="fade"][open]::backdrop, + .sui-dialog[data-motion="fade"][open][data-closing], + .sui-dialog[data-motion="fade"][open][data-closing]::backdrop { animation: none; } } diff --git a/sushi/src/styles/features/fieldset.styles.css b/sushi/src/styles/features/fieldset.styles.css index 6b628c10..7305a4c6 100644 --- a/sushi/src/styles/features/fieldset.styles.css +++ b/sushi/src/styles/features/fieldset.styles.css @@ -25,7 +25,7 @@ .sui-fieldset__toggle::before { @apply size-4 shrink-0; - content: ''; + content: ""; background-color: currentColor; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-width='2' d='M5 12h14'/%3E%3C/svg%3E") center / contain no-repeat; diff --git a/sushi/src/styles/features/input.styles.css b/sushi/src/styles/features/input.styles.css index 93006bce..c1b03f9b 100644 --- a/sushi/src/styles/features/input.styles.css +++ b/sushi/src/styles/features/input.styles.css @@ -1,4 +1,4 @@ -:is(.sui-input, .sui-input-surface__control)[type='search']::-webkit-search-cancel-button { +:is(.sui-input, .sui-input-surface__control)[type="search"]::-webkit-search-cancel-button { width: 1rem; height: 1rem; appearance: none; diff --git a/sushi/src/styles/features/join.styles.css b/sushi/src/styles/features/join.styles.css index 6577ca3c..7fa15769 100644 --- a/sushi/src/styles/features/join.styles.css +++ b/sushi/src/styles/features/join.styles.css @@ -87,8 +87,8 @@ z-index: 1; } -.join > .join-item:is(.input-error, .select-error, .textarea-error, .file-input-error, [aria-invalid='true']), -.join > .join-item:has(.input-error, .select-error, .textarea-error, .file-input-error, [aria-invalid='true']) { +.join > .join-item:is(.input-error, .select-error, .textarea-error, .file-input-error, [aria-invalid="true"]), +.join > .join-item:has(.input-error, .select-error, .textarea-error, .file-input-error, [aria-invalid="true"]) { z-index: 2; } diff --git a/sushi/src/styles/features/lightbox.styles.css b/sushi/src/styles/features/lightbox.styles.css index ccb33484..8d706df8 100644 --- a/sushi/src/styles/features/lightbox.styles.css +++ b/sushi/src/styles/features/lightbox.styles.css @@ -1,4 +1,4 @@ -@import 'photoswipe/style.css' layer(components); +@import "photoswipe/style.css" layer(components); @layer components { .sui-lightbox-trigger { diff --git a/sushi/src/styles/features/navbar.styles.css b/sushi/src/styles/features/navbar.styles.css index d39ffa1f..cc7ce2ae 100644 --- a/sushi/src/styles/features/navbar.styles.css +++ b/sushi/src/styles/features/navbar.styles.css @@ -2,7 +2,7 @@ container-type: inline-size; } -.sui-navbar-container[data-collapsible='false'][data-orientation='horizontal'] { +.sui-navbar-container[data-collapsible="false"][data-orientation="horizontal"] { container-type: normal; width: max-content; min-width: 100%; @@ -97,21 +97,21 @@ @apply flex; } -.sui-navbar[data-collapsible='false'] > .sui-navbar__content { +.sui-navbar[data-collapsible="false"] > .sui-navbar__content { @apply flex; } -.sui-navbar--horizontal[data-collapsible='false'] { +.sui-navbar--horizontal[data-collapsible="false"] { @apply flex-nowrap gap-2; } -.sui-navbar--horizontal[data-collapsible='false'] > :where(.sui-navbar__content) { +.sui-navbar--horizontal[data-collapsible="false"] > :where(.sui-navbar__content) { @apply static order-none m-0 flex w-auto basis-auto flex-row border-0 bg-transparent p-0; align-items: center; flex: none; } -.sui-navbar--horizontal.sui-navbar--menu-center[data-collapsible='false'] > sui-navbar-menu { +.sui-navbar--horizontal.sui-navbar--menu-center[data-collapsible="false"] > sui-navbar-menu { position: absolute; left: 50%; margin-inline-start: 0; @@ -161,12 +161,12 @@ outline-offset: var(--sui-focus-ring-offset); } -.sui-navbar__link[aria-current='page'] { +.sui-navbar__link[aria-current="page"] { background-color: var(--sui-navbar-item-active-background); color: var(--sui-navbar-item-active-color); } -.sui-navbar__links--links > li > .sui-navbar__link[aria-current='page'] { +.sui-navbar__links--links > li > .sui-navbar__link[aria-current="page"] { @apply rounded-none bg-transparent no-underline; } @@ -174,8 +174,8 @@ border-radius: var(--radius-field); } -.sui-navbar__links--links > li > .sui-navbar__link[aria-current='page'] > .sui-navbar__label, -.sui-navbar__links--links > li > .sui-navbar__dropdown > summary:is([aria-current='page'], [data-child-current]) .sui-navbar__label { +.sui-navbar__links--links > li > .sui-navbar__link[aria-current="page"] > .sui-navbar__label, +.sui-navbar__links--links > li > .sui-navbar__dropdown > summary:is([aria-current="page"], [data-child-current]) .sui-navbar__label { @apply underline decoration-2 underline-offset-8; } @@ -184,8 +184,8 @@ color: var(--sui-navbar-link-hover-color); } -.sui-navbar__link[aria-disabled='true'], -.sui-navbar__dropdown > summary[aria-disabled='true'] { +.sui-navbar__link[aria-disabled="true"], +.sui-navbar__dropdown > summary[aria-disabled="true"] { @apply cursor-not-allowed opacity-40; } @@ -218,12 +218,12 @@ outline-offset: var(--sui-focus-ring-offset); } -.sui-navbar__dropdown > summary:is([aria-current='page'], [data-child-current]) { +.sui-navbar__dropdown > summary:is([aria-current="page"], [data-child-current]) { background-color: var(--sui-navbar-item-active-background); color: var(--sui-navbar-item-active-color); } -.sui-navbar__links--links > li > .sui-navbar__dropdown > summary:is([aria-current='page'], [data-child-current]) { +.sui-navbar__links--links > li > .sui-navbar__dropdown > summary:is([aria-current="page"], [data-child-current]) { @apply rounded-none bg-transparent no-underline; } @@ -245,45 +245,45 @@ } @container (min-width: 64rem) { - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar { + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar { @apply flex-nowrap; } - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar__toggle { + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar__toggle { @apply hidden; } - .sui-navbar-container[data-collapse-at='lg'] :where(.sui-navbar__content) { + .sui-navbar-container[data-collapse-at="lg"] :where(.sui-navbar__content) { @apply static order-none m-0 flex w-auto basis-auto flex-row border-0 bg-transparent p-0; align-items: center; } - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar__content > sui-navbar-menu { + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar__content > sui-navbar-menu { margin-inline-start: auto; } - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar__menu-offset { + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar__menu-offset { margin-inline-start: 0.25rem; } - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar__content .sui-navbar__links { + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar__content .sui-navbar__links { width: auto; } - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar__content .sui-navbar__links > li, - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar__content .sui-navbar__links > li > :where(a, button) { + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar__content .sui-navbar__links > li, + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar__content .sui-navbar__links > li > :where(a, button) { width: auto; } - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar__dropdown > ul { + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar__dropdown > ul { @apply absolute top-full left-0 z-30 mt-2 w-auto min-w-52 rounded-box border border-base-300 bg-base-100 p-2; } - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar__links--responsive { + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar__links--responsive { flex-direction: row; } - .sui-navbar-container[data-collapse-at='lg'] .sui-navbar--menu-center > .sui-navbar__content { + .sui-navbar-container[data-collapse-at="lg"] .sui-navbar--menu-center > .sui-navbar__content { position: absolute; left: 50%; width: auto; @@ -293,45 +293,45 @@ } @container (min-width: 48rem) { - .sui-navbar-container[data-collapse-at='md'] .sui-navbar { + .sui-navbar-container[data-collapse-at="md"] .sui-navbar { @apply flex-nowrap; } - .sui-navbar-container[data-collapse-at='md'] .sui-navbar__toggle { + .sui-navbar-container[data-collapse-at="md"] .sui-navbar__toggle { @apply hidden; } - .sui-navbar-container[data-collapse-at='md'] :where(.sui-navbar__content) { + .sui-navbar-container[data-collapse-at="md"] :where(.sui-navbar__content) { @apply static order-none m-0 flex w-auto basis-auto flex-row border-0 bg-transparent p-0; align-items: center; } - .sui-navbar-container[data-collapse-at='md'] .sui-navbar__content > sui-navbar-menu { + .sui-navbar-container[data-collapse-at="md"] .sui-navbar__content > sui-navbar-menu { margin-inline-start: auto; } - .sui-navbar-container[data-collapse-at='md'] .sui-navbar__menu-offset { + .sui-navbar-container[data-collapse-at="md"] .sui-navbar__menu-offset { margin-inline-start: 0.25rem; } - .sui-navbar-container[data-collapse-at='md'] .sui-navbar__content .sui-navbar__links { + .sui-navbar-container[data-collapse-at="md"] .sui-navbar__content .sui-navbar__links { width: auto; } - .sui-navbar-container[data-collapse-at='md'] .sui-navbar__content .sui-navbar__links > li, - .sui-navbar-container[data-collapse-at='md'] .sui-navbar__content .sui-navbar__links > li > :where(a, button) { + .sui-navbar-container[data-collapse-at="md"] .sui-navbar__content .sui-navbar__links > li, + .sui-navbar-container[data-collapse-at="md"] .sui-navbar__content .sui-navbar__links > li > :where(a, button) { width: auto; } - .sui-navbar-container[data-collapse-at='md'] .sui-navbar__dropdown > ul { + .sui-navbar-container[data-collapse-at="md"] .sui-navbar__dropdown > ul { @apply absolute top-full left-0 z-30 mt-2 w-auto min-w-52 rounded-box border border-base-300 bg-base-100 p-2; } - .sui-navbar-container[data-collapse-at='md'] .sui-navbar__links--responsive { + .sui-navbar-container[data-collapse-at="md"] .sui-navbar__links--responsive { flex-direction: row; } - .sui-navbar-container[data-collapse-at='md'] .sui-navbar--menu-center > .sui-navbar__content { + .sui-navbar-container[data-collapse-at="md"] .sui-navbar--menu-center > .sui-navbar__content { position: absolute; left: 50%; width: auto; diff --git a/sushi/src/styles/features/select-button.styles.css b/sushi/src/styles/features/select-button.styles.css index 9a4050ce..bf482ca5 100644 --- a/sushi/src/styles/features/select-button.styles.css +++ b/sushi/src/styles/features/select-button.styles.css @@ -1,3 +1,3 @@ -.sui-select-button > button[aria-checked='true'] { +.sui-select-button > button[aria-checked="true"] { z-index: 2; } diff --git a/sushi/src/styles/features/selection.styles.css b/sushi/src/styles/features/selection.styles.css index 720c1ee2..49b5353b 100644 --- a/sushi/src/styles/features/selection.styles.css +++ b/sushi/src/styles/features/selection.styles.css @@ -9,45 +9,45 @@ @apply select-none text-base font-medium transition-colors duration-150; } -.sui-selection-list[aria-disabled='true'] { +.sui-selection-list[aria-disabled="true"] { @apply cursor-not-allowed bg-base-200 text-base-content/40 select-none; } -.sui-selection-list[aria-disabled='true'] * { +.sui-selection-list[aria-disabled="true"] * { cursor: inherit; } -.sui-selection-option:not([aria-disabled='true']):not([aria-selected='true'], [data-matched='true']):hover, -.sui-selection-list:not(.sui-selection-list--focus-bound) .sui-selection-option:not([aria-disabled='true']):not([aria-selected='true'], [data-matched='true'])[data-active='true'], -.sui-selection-list--focus-bound:focus .sui-selection-option:not([aria-disabled='true']):not([aria-selected='true'], [data-matched='true'])[data-active='true'] { +.sui-selection-option:not([aria-disabled="true"]):not([aria-selected="true"], [data-matched="true"]):hover, +.sui-selection-list:not(.sui-selection-list--focus-bound) .sui-selection-option:not([aria-disabled="true"]):not([aria-selected="true"], [data-matched="true"])[data-active="true"], +.sui-selection-list--focus-bound:focus .sui-selection-option:not([aria-disabled="true"]):not([aria-selected="true"], [data-matched="true"])[data-active="true"] { /** Background of an enabled option when hovered or actively navigated. */ background-color: var(--sui-selection-option-hover-background, var(--color-base-200)); /** Text color of an enabled option when hovered or actively navigated. */ color: var(--sui-selection-option-hover-color, var(--color-base-content)); } -.sui-selection-option:not([aria-disabled='true']):not([aria-selected='true'], [data-matched='true']):active { +.sui-selection-option:not([aria-disabled="true"]):not([aria-selected="true"], [data-matched="true"]):active { /** Background of an enabled option while pressed. */ background-color: var(--sui-selection-option-active-background, var(--sui-selection-option-hover-background, var(--color-base-200))); /** Text color of an enabled option while pressed. */ color: var(--sui-selection-option-active-color, var(--sui-selection-option-hover-color, var(--color-base-content))); } -.sui-selection-option:is([aria-selected='true'], [data-matched='true']) { +.sui-selection-option:is([aria-selected="true"], [data-matched="true"]) { /** Background of a selected or matched option. */ background-color: var(--sui-selection-option-selected-background, color-mix(in oklab, var(--color-base-content) 10%, transparent)); /** Text color of a selected or matched option. */ color: var(--sui-selection-option-selected-color, var(--color-base-content)); } -.sui-selection-option:not([aria-disabled='true']):is([aria-selected='true'], [data-matched='true']):hover, -.sui-selection-list:not(.sui-selection-list--focus-bound) .sui-selection-option:not([aria-disabled='true']):is([aria-selected='true'], [data-matched='true'])[data-active='true'], -.sui-selection-list--focus-bound:focus .sui-selection-option:not([aria-disabled='true']):is([aria-selected='true'], [data-matched='true'])[data-active='true'] { +.sui-selection-option:not([aria-disabled="true"]):is([aria-selected="true"], [data-matched="true"]):hover, +.sui-selection-list:not(.sui-selection-list--focus-bound) .sui-selection-option:not([aria-disabled="true"]):is([aria-selected="true"], [data-matched="true"])[data-active="true"], +.sui-selection-list--focus-bound:focus .sui-selection-option:not([aria-disabled="true"]):is([aria-selected="true"], [data-matched="true"])[data-active="true"] { /** Background of a selected option when hovered, pressed, or actively navigated. */ background-color: var(--sui-selection-option-selected-hover-background, color-mix(in oklab, var(--color-base-content) 15%, transparent)); } -.sui-selection-list.sui-selection-list[aria-disabled='true'] .sui-selection-option { +.sui-selection-list.sui-selection-list[aria-disabled="true"] .sui-selection-option { pointer-events: none; background-color: transparent; color: inherit; diff --git a/sushi/src/styles/features/sidebar.styles.css b/sushi/src/styles/features/sidebar.styles.css index e8ce089a..906ad20c 100644 --- a/sushi/src/styles/features/sidebar.styles.css +++ b/sushi/src/styles/features/sidebar.styles.css @@ -13,7 +13,7 @@ color: var(--sui-sidebar-item-hover-color); } -.sui-sidebar__rail-item:is([aria-current='page'], [data-child-current]) { +.sui-sidebar__rail-item:is([aria-current="page"], [data-child-current]) { background-color: var(--sui-sidebar-item-active-background); color: var(--sui-sidebar-item-active-color); } @@ -23,6 +23,6 @@ outline-offset: var(--sui-focus-ring-offset); } -.sui-sidebar__rail-item:is([aria-disabled='true'], :disabled) { +.sui-sidebar__rail-item:is([aria-disabled="true"], :disabled) { @apply cursor-not-allowed opacity-40; } diff --git a/sushi/src/styles/features/tabs.styles.css b/sushi/src/styles/features/tabs.styles.css index 1165d1ef..3e66de14 100644 --- a/sushi/src/styles/features/tabs.styles.css +++ b/sushi/src/styles/features/tabs.styles.css @@ -17,7 +17,7 @@ } .sui-tabs__list.tabs-box, -.sui-tabs__list.tabs-box > .sui-tab:is(.tab-active, [aria-selected='true']) { +.sui-tabs__list.tabs-box > .sui-tab:is(.tab-active, [aria-selected="true"]) { box-shadow: none; } diff --git a/sushi/src/styles/features/tooltip.styles.css b/sushi/src/styles/features/tooltip.styles.css index ab8a570b..751c47c3 100644 --- a/sushi/src/styles/features/tooltip.styles.css +++ b/sushi/src/styles/features/tooltip.styles.css @@ -28,27 +28,27 @@ position: absolute; width: 0.5rem; height: 0.5rem; - content: ''; + content: ""; background: var(--sui-tooltip-background); transform: rotate(45deg); } - .sui-tooltip[data-placement='top']::before { + .sui-tooltip[data-placement="top"]::before { bottom: -0.2rem; left: calc(50% - 0.25rem); } - .sui-tooltip[data-placement='right']::before { + .sui-tooltip[data-placement="right"]::before { top: calc(50% - 0.25rem); left: -0.2rem; } - .sui-tooltip[data-placement='bottom']::before { + .sui-tooltip[data-placement="bottom"]::before { top: -0.2rem; left: calc(50% - 0.25rem); } - .sui-tooltip[data-placement='left']::before { + .sui-tooltip[data-placement="left"]::before { top: calc(50% - 0.25rem); right: -0.2rem; } diff --git a/sushi/src/styles/sushi.core.css b/sushi/src/styles/sushi.core.css index e11b355c..d1020fc5 100644 --- a/sushi/src/styles/sushi.core.css +++ b/sushi/src/styles/sushi.core.css @@ -1,25 +1,25 @@ -@import './features/button.styles.css'; -@import './features/divider.styles.css'; -@import './features/dialog.styles.css'; -@import './features/fieldset.styles.css'; -@import './features/focus.styles.css'; -@import './features/form-control.styles.css'; -@import './features/input-group.styles.css'; -@import './features/input.styles.css'; -@import './features/join.styles.css'; -@import './features/lightbox.styles.css'; -@import './features/kbd.styles.css'; -@import './features/label.styles.css'; -@import './features/message.styles.css'; -@import './features/navbar.styles.css'; -@import './features/progress.styles.css'; -@import './features/range.styles.css'; -@import './features/selection.styles.css'; -@import './features/sidebar.styles.css'; -@import './features/select-button.styles.css'; -@import './features/skeleton.styles.css'; -@import './features/tooltip.styles.css'; -@import './features/spinner.styles.css'; -@import './features/status.styles.css'; -@import './features/tabs.styles.css'; -@import './features/textarea.styles.css'; +@import "./features/button.styles.css"; +@import "./features/divider.styles.css"; +@import "./features/dialog.styles.css"; +@import "./features/fieldset.styles.css"; +@import "./features/focus.styles.css"; +@import "./features/form-control.styles.css"; +@import "./features/input-group.styles.css"; +@import "./features/input.styles.css"; +@import "./features/join.styles.css"; +@import "./features/lightbox.styles.css"; +@import "./features/kbd.styles.css"; +@import "./features/label.styles.css"; +@import "./features/message.styles.css"; +@import "./features/navbar.styles.css"; +@import "./features/progress.styles.css"; +@import "./features/range.styles.css"; +@import "./features/selection.styles.css"; +@import "./features/sidebar.styles.css"; +@import "./features/select-button.styles.css"; +@import "./features/skeleton.styles.css"; +@import "./features/tooltip.styles.css"; +@import "./features/spinner.styles.css"; +@import "./features/status.styles.css"; +@import "./features/tabs.styles.css"; +@import "./features/textarea.styles.css"; diff --git a/sushi/src/styles/sushi.source.css b/sushi/src/styles/sushi.source.css index b7208dc6..18987601 100644 --- a/sushi/src/styles/sushi.source.css +++ b/sushi/src/styles/sushi.source.css @@ -1,12 +1,12 @@ @layer theme, base, components, utilities; -@import 'tailwindcss/theme.css' layer(theme); -@import 'tailwindcss/utilities.css' layer(utilities) source(none); +@import "tailwindcss/theme.css" layer(theme); +@import "tailwindcss/utilities.css" layer(utilities) source(none); @source '../lib'; -@import './sushi.themes.css'; -@import './sushi.core.css'; +@import "./sushi.themes.css"; +@import "./sushi.core.css"; @plugin "daisyui" { themes: diff --git a/sushi/src/styles/sushi.themes.css b/sushi/src/styles/sushi.themes.css index e6cd7f5e..1191b688 100644 --- a/sushi/src/styles/sushi.themes.css +++ b/sushi/src/styles/sushi.themes.css @@ -1,5 +1,5 @@ @plugin "daisyui/theme" { - name: 'sushi'; + name: "sushi"; default: true; prefersdark: false; color-scheme: light; @@ -98,7 +98,7 @@ } @plugin "daisyui/theme" { - name: 'sushi-dark'; + name: "sushi-dark"; default: false; prefersdark: true; color-scheme: dark; diff --git a/sushi/testing/setup.ts b/sushi/testing/setup.ts index 0a086d93..4416dea2 100644 --- a/sushi/testing/setup.ts +++ b/sushi/testing/setup.ts @@ -1,5 +1,5 @@ class ResizeObserverStub implements ResizeObserver { - private readonly targets: Set = new Set(); + private readonly targets: Set = new Set(); public disconnect(): void { this.targets.clear(); diff --git a/tests/package-install/src/main.ts b/tests/package-install/src/main.ts index 11d0e849..6b7170f8 100644 --- a/tests/package-install/src/main.ts +++ b/tests/package-install/src/main.ts @@ -1,10 +1,11 @@ -import { Component, signal, WritableSignal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal, type WritableSignal } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; import { Button, Chip, ToggleButton } from '@sushi-kit/angular'; @Component({ selector: 'app-root', imports: [Button, Chip, ToggleButton], + changeDetection: ChangeDetectionStrategy.OnPush, template: ` diff --git a/tests/package-install/src/styles.css b/tests/package-install/src/styles.css index 57c6fcc2..219b472b 100644 --- a/tests/package-install/src/styles.css +++ b/tests/package-install/src/styles.css @@ -1 +1 @@ -@import '@sushi-kit/angular/styles.css'; +@import "@sushi-kit/angular/styles.css"; diff --git a/tests/package-install/tsconfig.json b/tests/package-install/tsconfig.json index 65b7263c..302bf56a 100644 --- a/tests/package-install/tsconfig.json +++ b/tests/package-install/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "bundler", "lib": ["ES2022", "dom"], "strict": true, + "noUncheckedIndexedAccess": true, "skipLibCheck": true, "experimentalDecorators": true, "isolatedModules": true, diff --git a/tools/api-reference/generated-file.ts b/tools/api-reference/generated-file.ts index 224833bd..a52b2d07 100644 --- a/tools/api-reference/generated-file.ts +++ b/tools/api-reference/generated-file.ts @@ -13,8 +13,6 @@ export interface GeneratedApi { const GENERATED_BANNER: string = '/* Generated by tools/generate-api-reference.ts. Do not edit. */'; const API_TYPES_IMPORT: string = "import type { ApiReferenceData } from '../shared/api-reference/api-reference.types';"; -// Documentation validation - function findMissingTypeDocumentation(reference: ApiReferenceData): string[] { const missing: string[] = []; for (const type of reference.types) { @@ -55,8 +53,6 @@ function validateDocumentation(data: Readonly>) if (missing.length) throw new Error(`Missing public API documentation: ${missing.join(', ')}`); } -// Generated TypeScript source - function renderReferenceShape(references: Readonly>): string { return Object.keys(references) .map((name: string): string => `readonly ${name}: ApiReferenceData;`) diff --git a/tools/api-reference/generator.spec.ts b/tools/api-reference/generator.spec.ts index f5092419..deac2a48 100644 --- a/tools/api-reference/generator.spec.ts +++ b/tools/api-reference/generator.spec.ts @@ -96,21 +96,27 @@ afterEach((): void => { } }); +function requireReference(result: GeneratedApi, name: string): ApiReferenceData { + const reference: ApiReferenceData | undefined = result.references[name]; + if (!reference) throw new Error(`Expected generated API reference for ${name}.`); + return reference; +} + describe('API reference exports', (): void => { it('documents exported components and omits internal or unexported symbols', (): void => { const result: GeneratedApi = generateApiReference(createFixture()).data; expect(Object.keys(result.references)).toEqual(['Example', 'Secondary']); - expect(result.references['Example'].selector).toBe('sui-example'); + expect(requireReference(result, 'Example').selector).toBe('sui-example'); expect(result.referencesByRoute['example']).toEqual(['Example', 'Secondary']); - expect(result.references['Secondary'].templates).toEqual([]); - expect(result.references['Secondary'].types).toEqual([]); + expect(requireReference(result, 'Secondary').templates).toEqual([]); + expect(requireReference(result, 'Secondary').types).toEqual([]); }); }); describe('API reference types and templates', (): void => { it('resolves generic, nested, template-context, and platform types', (): void => { - const example: ApiReferenceData = generateApiReference(createFixture()).data.references['Example']; + const example: ApiReferenceData = requireReference(generateApiReference(createFixture()).data, 'Example'); expect(example.declaration).toBe('Example'); expect(example.typeParameters).toEqual([{ name: 'T', constraint: null, defaultValue: 'string' }]); @@ -136,7 +142,7 @@ describe('API reference types and templates', (): void => { describe('API reference CSS tokens', (): void => { it('includes declarations and fallback-only token references', (): void => { - const example: ApiReferenceData = generateApiReference(createFixture()).data.references['Example']; + const example: ApiReferenceData = requireReference(generateApiReference(createFixture()).data, 'Example'); expect(example.styles).toContainEqual(expect.objectContaining({ name: '--sui-example-gap', defaultValue: '1rem' })); expect(example.styles).toContainEqual( diff --git a/tools/api-reference/generator.ts b/tools/api-reference/generator.ts index 788dd15e..29bcdbfe 100644 --- a/tools/api-reference/generator.ts +++ b/tools/api-reference/generator.ts @@ -57,16 +57,7 @@ const GENERATED_FILE: string = 'playground/src/app/generated/api-reference.gener const STYLE_FILE_SUFFIX: string = '.styles.css'; const TEMPLATE_SELECTOR_PREFIX: string = 'ng-template['; const STYLE_ROUTE_DIRECTIVE: string = '@sui-docs'; -const PLATFORM_TYPE_EXCLUSIONS: ReadonlySet = new Set([ - 'Array', - 'Map', - 'Promise', - 'ReadonlyArray', - 'Record', - 'Set', -]); - -// Workspace discovery +const PLATFORM_TYPE_EXCLUSIONS: ReadonlySet = new Set(['Array', 'Map', 'Promise', 'ReadonlyArray', 'Record', 'Set']); export function createApiReferencePaths(root: string): ApiReferencePaths { return { @@ -85,7 +76,7 @@ function toKebabCase(value: string): string { function createRouteResolver(root: string): (className: string, folder: string) => string { const pageEntries: readonly Dirent[] = readdirSync(join(root, PAGE_DIRECTORY), { withFileTypes: true }); - const routes: Set = new Set(); + const routes: Set = new Set(); for (const entry of pageEntries) { if (entry.isDirectory()) routes.add(entry.name); } @@ -97,7 +88,7 @@ function createRouteResolver(root: string): (className: string, folder: string) } function discoverGlobalStyles(root: string): ReadonlyMap { - const filesByRoute: Map = new Map(); + const filesByRoute: Map = new Map(); const styleFolder: string = join(root, STYLE_DIRECTORY); for (const entry of readdirSync(styleFolder, { withFileTypes: true })) { if (!entry.isFile() || !entry.name.endsWith(STYLE_FILE_SUFFIX)) continue; @@ -125,8 +116,6 @@ function readAdditionalStyleRoutes(stylesheet: Root): readonly string[] { return []; } -// TypeScript program and source collection - function loadProgram(root: string): ts.Program { const configFile: string = join(root, TYPESCRIPT_CONFIG_FILE); const config: ts.ParsedCommandLine | undefined = ts.getParsedCommandLineOfConfigFile( @@ -167,7 +156,7 @@ function findPublicDeclarations(root: string, program: ts.Program, checker: ts.T } function findPlatformTypes(program: ts.Program): ReadonlyMap { - const declarations: Map = new Map(); + const declarations: Map = new Map(); for (const sourceFile of program.getSourceFiles()) { if (!sourceFile.isDeclarationFile || !/^lib\..+\.d\.ts$/u.test(basename(sourceFile.fileName))) continue; @@ -251,8 +240,6 @@ function collectDeclarations( } } -// Final API model - function templatesFor(reference: ComponentReference, collection: Collection): readonly ApiTemplate[] { const templates: readonly ApiTemplate[] = collection.templates.get(reference.folder) ?? []; const siblings: readonly ComponentReference[] = collection.references.filter( @@ -265,7 +252,7 @@ function templatesFor(reference: ComponentReference, collection: Collection): re } function typesFor(names: readonly string[], collection: Collection, reader: ApiSourceReader): readonly ApiTypeDefinition[] { - const definitions: Map = new Map(collection.types); + const definitions: Map = new Map(collection.types); for (const name of names) { if (definitions.has(name) || PLATFORM_TYPE_EXCLUSIONS.has(name)) continue; diff --git a/tools/api-reference/source-reader.ts b/tools/api-reference/source-reader.ts index 400f2fda..81cc136f 100644 --- a/tools/api-reference/source-reader.ts +++ b/tools/api-reference/source-reader.ts @@ -38,7 +38,7 @@ type ApiTypeNode = ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDe type TypeParameterOwner = ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeAliasDeclaration; // Syntax conventions understood by the reader. Keep framework-specific names centralized here. -const ANGULAR_DECLARATION_DECORATORS: ReadonlySet = new Set(['Component', 'Directive']); +const ANGULAR_DECLARATION_DECORATORS: ReadonlySet = new Set(['Component', 'Directive']); const INJECTABLE_DECORATOR: string = 'Injectable'; const SELECTOR_METADATA_PROPERTY: string = 'selector'; const STYLE_URL_METADATA_PROPERTY: string = 'styleUrl'; @@ -47,7 +47,7 @@ const TEMPLATE_CONTEXT_GUARD: string = 'ngTemplateContextGuard'; const TEMPLATE_CONTEXT_PROPERTY: string = 'ngTemplateContextType'; const INTERNAL_JSDOC_TAG: string = 'internal'; const JSDOC_OPENING: string = '/**'; -const SIGNAL_KINDS: ReadonlyMap = new Map([ +const SIGNAL_KINDS: ReadonlyMap = new Map([ ['input', 'input'], ['input.required', 'input'], ['model', 'model'], @@ -58,8 +58,6 @@ const STYLE_PROPERTY_PREFIX: string = '--sui-'; const STYLE_REFERENCE_PATTERN: RegExp = /var\((--sui-[\w-]+),/u; const STYLE_EXAMPLE_PATTERN: RegExp = /^@example\s+(.+)$/mu; -// Style token documentation - function precedingDocumentation(declaration: Declaration): string | null { const previous: ChildNode | undefined = declaration.prev(); if (previous?.type !== 'comment') return null; @@ -91,11 +89,12 @@ function stylePropertyFrom(declaration: Declaration): ApiStyleProperty | null { } const reference: RegExpExecArray | null = STYLE_REFERENCE_PATTERN.exec(declaration.value); - return reference ? parseStyleProperty(reference[1], 'theme default', documentation) : null; + const name: string | undefined = reference?.[1]; + return name ? parseStyleProperty(name, 'theme default', documentation) : null; } function readStyleProperties(files: readonly string[]): readonly ApiStyleProperty[] { - const properties: Map = new Map(); + const properties: Map = new Map(); for (const file of files) { const stylesheet: Root = postcss.parse(readFileSync(file, 'utf8'), { from: file }); stylesheet.walkDecls((declaration: Declaration): void => { @@ -107,8 +106,6 @@ function readStyleProperties(files: readonly string[]): readonly ApiStylePropert return [...properties.values()]; } -// Angular metadata and class declarations - export function readClassDeclaration(node: ts.ClassDeclaration): string { const className: string = node.name?.text ?? ''; const parameters: string = @@ -218,8 +215,6 @@ export function isInternal(node: ts.Node): boolean { return ts.getJSDocTags(node).some((tag: ts.JSDocTag): boolean => tag.tagName.text === INTERNAL_JSDOC_TAG); } -// Public inputs, models, outputs and methods - function isPublic(node: ts.Node & { readonly modifiers?: ts.NodeArray }): boolean { return !node.modifiers?.some( (modifier: ts.ModifierLike): boolean => @@ -329,7 +324,7 @@ function findBaseClass(node: ts.ClassDeclaration, checker: ts.TypeChecker): ts.C } export function readMembers(node: ts.ClassDeclaration, checker: ts.TypeChecker): ApiMember[] { - const members: Map = new Map(); + const members: Map = new Map(); let current: ts.ClassDeclaration | null = node; while (current) { for (const member of readDeclaredMembers(current, checker)) { @@ -355,8 +350,6 @@ export function readTemplateContext(node: ts.ClassDeclaration): string { return context && ts.isPropertyDeclaration(context) ? (context.type?.getText() ?? 'void') : 'void'; } -// Exported interfaces, aliases and enums - function isExported(node: ts.Node): boolean { return Boolean( ts.canHaveModifiers(node) && @@ -455,8 +448,8 @@ function addTypeSymbols(type: ts.Type, checker: ts.TypeChecker, names: Set = new Set(); - const visited: Set = new Set(); + const names: Set = new Set(); + const visited: Set = new Set(); const visit: (child: ts.Node) => void = (child: ts.Node): void => { if (ts.isTypeReferenceNode(child)) { const referencedSymbol: ts.Symbol | undefined = checker.getSymbolAtLocation(child.typeName); @@ -499,7 +492,7 @@ function addClassTypeNames(node: ts.ClassDeclaration, checker: ts.TypeChecker, n } function publicMemberTypeNames(node: ts.ClassDeclaration, checker: ts.TypeChecker): readonly string[] { - const names: Set = new Set(); + const names: Set = new Set(); let current: ts.ClassDeclaration | null = node; while (current) { addClassTypeNames(current, checker, names); @@ -548,7 +541,7 @@ export class ApiSourceReader { dependencies: ReadonlyMap, ): readonly ApiTypeDefinition[] { const pending: string[] = [...initialNames]; - const found: Map = new Map(); + const found: Map = new Map(); while (pending.length) { const name: string | undefined = pending.shift(); const definition: ApiTypeDefinition | undefined = name ? definitions.get(name) : undefined; diff --git a/tools/generate-third-party-notices.ts b/tools/generate-third-party-notices.ts index c9fa4a56..36c9ade3 100644 --- a/tools/generate-third-party-notices.ts +++ b/tools/generate-third-party-notices.ts @@ -9,14 +9,14 @@ const PACKAGE_DIRECTIVE: RegExp = /@(?:import|plugin|reference)\s+["']([^"']+)[" const LICENSE_FILE: RegExp = /^(licen[cs]e|copying)/iu; interface PackageManifest { - version: string; + readonly version: string; } function toPackageName(specifier: string): string | null { if (/^[./]|^https?:/u.test(specifier)) return null; const segments: string[] = specifier.split('/'); - return specifier.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0]; + return specifier.startsWith('@') ? segments.slice(0, 2).join('/') : (segments[0] ?? null); } async function collectPackages(): Promise { @@ -25,12 +25,13 @@ async function collectPackages(): Promise { .filter((entry: Dirent): boolean => entry.isFile() && entry.name.endsWith('.css')) .map((entry: Dirent): string => resolve(entry.parentPath, entry.name)); - const packages: Set = new Set(); + const packages: Set = new Set(); for (const file of styleFiles) { const content: string = await readFile(file, 'utf8'); for (const [, specifier] of content.matchAll(PACKAGE_DIRECTIVE)) { + if (!specifier) continue; const packageName: string | null = toPackageName(specifier); if (packageName) packages.add(packageName); } @@ -41,7 +42,11 @@ async function collectPackages(): Promise { async function readManifest(packageDir: string): Promise { const content: string = await readFile(resolve(packageDir, 'package.json'), 'utf8'); - return JSON.parse(content) as PackageManifest; + const manifest: unknown = JSON.parse(content); + if (typeof manifest !== 'object' || manifest === null || !('version' in manifest) || typeof manifest.version !== 'string') { + throw new Error(`Expected a package version in ${packageDir}/package.json.`); + } + return { version: manifest.version }; } async function readNotice(packageName: string): Promise { diff --git a/tools/theme-reference.ts b/tools/theme-reference.ts index 51f34dd6..1d4cf98c 100644 --- a/tools/theme-reference.ts +++ b/tools/theme-reference.ts @@ -54,7 +54,7 @@ function documentedToken(declaration: Declaration): ThemeTokenDocumentation | nu } function valuesFrom(theme: AtRule): ReadonlyMap { - const values: Map = new Map(); + const values: Map = new Map(); theme.walkDecls((declaration: Declaration): void => { if (declaration.prop.startsWith('--')) values.set(declaration.prop, declaration.value); }); diff --git a/tsconfig.json b/tsconfig.json index c4d70a61..5d32bea3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compileOnSave": false, "compilerOptions": { "strict": true, + "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, From 81d1fa32958c4b51736f0a09db846b655cb79e6e Mon Sep 17 00:00:00 2001 From: Sebastian Enns Date: Fri, 16 Oct 2026 01:12:40 +0200 Subject: [PATCH 3/4] chore(sushi): document conventions and add local type checks --- docs/coding-conventions.md | 102 +++++++++++++++++++++++++----------- docs/testing-conventions.md | 19 +++---- package.json | 3 +- 3 files changed, 82 insertions(+), 42 deletions(-) diff --git a/docs/coding-conventions.md b/docs/coding-conventions.md index c43ba9cf..bba90cb6 100644 --- a/docs/coding-conventions.md +++ b/docs/coding-conventions.md @@ -1,24 +1,25 @@ # Coding standards -## Component design +The formatter, compiler and ESLint configuration enforce mechanical rules. Review remains responsible for design, public contracts and semantic grouping. -- Prefer native HTML, then a directive, then a component. -- Reuse an existing Sushi feature before adding parallel behavior. -- Keep public APIs independent of DaisyUI and Tailwind. -- Support keyboard, pointer, touch, screen readers, zoom, narrow viewports, and translated text. -- Expose only stable consumer concepts as inputs, models, outputs, templates, methods, or CSS tokens. +## Design and responsibilities -## Angular +- Choose the simplest explicit solution for the current requirement. Do not add speculative extension points, generic frameworks or trivial wrappers. +- Give each module one coherent responsibility and a small interface. Extract shared code only when the responsibility is actually shared. +- Prefer native HTML, then a directive, then a component. Reuse an existing Sushi feature before adding parallel behavior. +- Keep public APIs independent of DaisyUI and Tailwind. Export only supported consumer contracts, never `internal/` declarations. +- Support keyboard, pointer, touch, screen readers, zoom, narrow viewports and translated text. + +## Angular and member order - Use signal APIs: `input`, `model`, `output`, `signal`, `computed`, and signal queries. - Use `inject()` and `ChangeDetectionStrategy.OnPush`. - Use Signal Forms for form controls; do not add CVA, `FormsModule`, or Reactive Forms. -- Prefer Angular Aria for interaction patterns and CDK for overlays, focus, portals, drag/drop, and virtual scrolling. -- Derive state with `computed`. Use an effect only to synchronize with an external system. -- Keep templates declarative and use early returns to keep branches shallow. -- Type public contracts and named variables. Do not introduce `any`. +- Prefer Angular Aria for interaction patterns and CDK for overlays, focus, portals, drag/drop and virtual scrolling. Use their public APIs. +- Derive values with `computed` instead of maintaining duplicate state. Use effects to synchronize with external systems. +- Keep templates declarative and give mutable state a clear owner. -Order class members as follows: +Order class members as follows, preserving initialization dependencies: 1. models, inputs, and outputs; 2. content and view queries; @@ -29,30 +30,67 @@ Order class members as follows: 7. protected handlers; 8. private helpers. -## Files and names +## Types and contracts + +- Use strict TypeScript checking, including `noUncheckedIndexedAccess` and unknown catch variables. Validate collection entries before use. +- Declare variable, field, parameter and return types explicitly, including primitive locals. Contextually typed callbacks and destructuring may use inference where ESLint permits it. +- Put generic constructor arguments on the declaration: `const values: Map = new Map();`. Do not repeat them on the constructor. +- Use `import type` or inline `type` imports for type-only dependencies. Keep decorators, injection tokens and other runtime values as normal imports. +- Do not use `any`, expression non-null assertions or unchecked casts to bypass an unclear contract. Narrow `unknown` at untrusted boundaries; annotations do not validate JSON or imported data. +- Prefer literal and discriminated unions for finite states. Use interfaces for object contracts and type aliases for unions and compositions. +- Declare visibility explicitly and mark references, fields and collections `readonly` when consumers should not mutate them. This does not imply a runtime freeze. +- Keep `null` and `undefined` consistent within each contract. Use `??` for absence and `||` only when every falsy value should select the fallback. +- Preserve public names, defaults, ordering and type compatibility during cleanup. Treat changes to them as API changes. + +## Files, names and imports -- Organize source by feature under `sushi/src/lib//`. -- Keep one primary declaration per file and specs under `testing/`. +- Organize library features under `sushi/src/lib//`; put specs under `testing/`. Keep one primary declaration per file; small helpers may stay with their only consumer. - Put code in `internal/` only when it has a separate private responsibility. -- Export public declarations through the feature `index.ts` and `sushi/src/public-api.ts`. -- Never export `internal/` declarations. -- Use kebab-case component selectors and camelCase directive attributes. -- Do not prefix public classes with `Sui`. Use positive names for boolean inputs. +- Export public declarations through the feature `index.ts` and `sushi/src/public-api.ts`. Use deliberate feature entry points, not internal barrels that hide cycles. +- Group imports as framework/runtime, external packages, then project modules. Use public package entry points and remove unused imports, including component metadata imports. +- Use PascalCase for types and classes, camelCase for functions and variables, plural names for collections, and names such as `itemsById` for lookups. +- Use concrete verbs and distinguish IDs, indexes, positions, counts and units. Shared fixed constants use UPPER_SNAKE_CASE; ordinary readonly values use camelCase. +- Use kebab-case component selectors and camelCase directive attributes. Do not prefix public classes with `Sui`, interfaces with `I` or private members with underscores. +- Use positive boolean names and preserve established public input names. Prefix intentionally unused required callback parameters with `_`. +- Write identifiers and technical documentation in English. Expose configurable labels where consumers need localisation. -## Public API and documentation +## Formatting and readability -- Prefer projection for content rendered once and typed templates for repeated or contextual content. -- Add consumer-facing JSDoc to every project-owned public declaration, member, template context, and type. -- Describe behavior, defaults, units, and constraints. Do not repeat the symbol name. -- Use only public APIs in examples. -- Do not maintain handwritten API tables; the generator reads TypeScript and CSS source. +Prettier owns wrapping, indentation and punctuation: two spaces, LF, final newline, semicolons, single-quoted TypeScript strings, double-quoted CSS strings, trailing commas and parenthesized arrow parameters. Preferred widths are 130 for TypeScript and 140 for Angular HTML; example HTML and CSS retain the overrides in `.prettierrc`. + +- Keep one step compact and use one blank line between distinct steps. Do not insert blank lines after every declaration, guard or `await`. +- Keep a lookup and its immediate guard together. Separate completed preparation or validation from the operation using it when they form distinct phases. +- Keep short lookup/check/return methods compact. After a substantial transformation or configuration object, separate the next operation when it has a different responsibility. +- Prefer guard clauses and explicit branches over deep nesting and nested ternaries. Name complex intermediate results rather than relying on wrapping alone. +- Extract a function when it owns a separate rule or its name expresses a useful operation. File length alone is not a reason to split code. +- Use comments for non-obvious reasons, invariants and compatibility constraints. Remove decorative section comments, dead code and obsolete TODOs. -## Readability +```ts +const option: SelectionOption | undefined = options[index]; +if (!option) return; -- Give each function one responsibility and keep it readable without tracing several nested branches. -- Separate completed steps with blank lines. -- Extract code when the extracted unit has a clear name and responsibility. -- Use comments for constraints and decisions, not for obvious syntax. -- Prefer direct control flow over generic helpers used once. +this.selectOption(option); +this.focus(); +``` + +## Async work, resources and errors + +- Await dependent work in order. Run independent work concurrently only when shared state permits it. +- Prevent duplicate work and stale results. After awaiting, check the relevant request, state or lifecycle before applying a result. +- Handle rejected promises, including deliberate fire-and-forget operations. Report unexpected UI failures at the Angular error boundary; do not silently replace them with fabricated success or fallback data. +- Keep cleanup with resource ownership: unsubscribe, disconnect observers, remove listeners, clear timers and cancel pending work. Prefer framework lifecycle tools. +- Distinguish expected absence, invalid input, cancellation and unexpected failure. Preserve valid state when an operation fails. +- Keep environment dependencies explicit and avoid import-time I/O outside bootstrap or tool entry points. Do not log secrets or complete user documents. + +## Public API, documentation and verification + +- Prefer projection for content rendered once and typed templates for repeated or contextual content. +- Add consumer-facing JSDoc to public declarations, members, template contexts and types. Describe behavior, defaults, units and constraints without narrating the signature. +- Use only public APIs in examples. Do not maintain handwritten API tables; the generator reads TypeScript and CSS sources. +- Keep focus visible, accessible names meaningful and focus restoration predictable. Respect reduced motion and long translated text. +- Test observable contracts and failure paths, not private implementation steps. Avoid redundant coverage of the same contract. +- Run formatting, lint, strict type checking, tests and production builds. Check the packaged artifact and installation in an isolated consumer for library changes. +- Keep formatter, lint, compiler configuration and documentation consistent. Do not weaken checks to accommodate an unclear design. +- Keep broad style migrations separate from behavior fixes when committing. Follow the repository's `type(scope): description` rules and validate commit messages. -See [Component styling](component-styling.md) for CSS placement and token rules. +See [Component styling](component-styling.md) and [Testing standards](testing-conventions.md) for the corresponding Sushi rules and verification commands. diff --git a/docs/testing-conventions.md b/docs/testing-conventions.md index 3e5a6747..22d562dd 100644 --- a/docs/testing-conventions.md +++ b/docs/testing-conventions.md @@ -72,14 +72,15 @@ To rerun only the isolated installation step, use `npm run package:install`. It ## Commands -| Command | Scope | -| ------------------------- | ------------------------------------------------------------------ | -| `npm test` | Library and documentation-tool unit tests | -| `npm run test:coverage` | Library units with coverage thresholds | -| `npm run test:tools` | Documentation generator units | -| `npm run test:e2e` | Browser contracts | -| `npm run package:check` | Build and check the package and both consumer builds | -| `npm run package:install` | Install the packed tarball in an isolated Angular app and build it | -| `npm run verify` | Complete handoff gate | +| Command | Scope | +| ------------------------- | ------------------------------------------------------------------------- | +| `npm run typecheck` | Strict TypeScript checks for library, fixtures, Playground, tools and E2E | +| `npm test` | Library and documentation-tool unit tests | +| `npm run test:coverage` | Library units with coverage thresholds | +| `npm run test:tools` | Documentation generator units | +| `npm run test:e2e` | Browser contracts | +| `npm run package:check` | Build and check the package and both consumer builds | +| `npm run package:install` | Install the packed tarball in an isolated Angular app and build it | +| `npm run verify` | Complete handoff gate | References: [Angular component testing](https://angular.dev/guide/testing/components-basics), [Angular testing scenarios](https://angular.dev/guide/testing/components-scenarios), and [Playwright best practices](https://playwright.dev/docs/best-practices). diff --git a/package.json b/package.json index 5682702b..c3d2f901 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "test:e2e:ui": "npm run build:playground && playwright test --config e2e/playwright.config.ts --ui", "test:tools": "vitest run tools", "test:watch": "ng test sushi", - "verify": "npm run lint && npm run format && npm run test:coverage && npm run test:tools && npm run test:e2e && npm run package:check" + "verify": "npm run lint && npm run format && npm run typecheck && npm run test:coverage && npm run test:tools && npm run test:e2e && npm run package:check", + "typecheck": "tsc -p sushi/tsconfig.lib.json --noEmit && tsc -p sushi/tsconfig.spec.json --noEmit && tsc -p playground/tsconfig.app.json --noEmit && tsc -p tools/tsconfig.json --noEmit && tsc -p e2e/tsconfig.json --noEmit" }, "private": true, "engines": { From f8b52e0a5236a928e20c4ff016e369ca9e4ea0ca Mon Sep 17 00:00:00 2001 From: Sebastian Enns Date: Fri, 16 Oct 2026 01:38:33 +0200 Subject: [PATCH 4/4] ci(sushi): check TypeScript types --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc2339c4..a1dc6710 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,8 @@ jobs: run: npm run lint - name: Format check run: npm run format + - name: Type check + run: npm run typecheck unit: name: Unit tests