Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9259955
build(angular): upgrading to angular 21
lillykoller Nov 21, 2025
b6940b5
feat: update to angular 21
lillykoller Nov 21, 2025
0105a03
feat(angular): using toThrow error instead of deprecated toThrowError
lillykoller Nov 21, 2025
cfb0fac
feat(angular): upgrading dependencies
lillykoller Nov 21, 2025
a5b375d
feat(angular): upgrading dependencies
lillykoller Nov 21, 2025
bcfeb26
build(release): next version [skip_build]
actions-user Nov 21, 2025
dad701a
docs(angular): adding angular version to readme
lillykoller Nov 21, 2025
80313b6
Merge remote-tracking branch 'origin/#63-update-angular' into #63-upd…
lillykoller Nov 21, 2025
57f53b9
feat(angular): dependencies
lillykoller Nov 21, 2025
584e9f0
build(release): next version [skip_build]
actions-user Nov 21, 2025
7108822
feat(angular): updating to angular 21
lillykoller Nov 25, 2025
4b1df63
Merge remote-tracking branch 'origin/#63-update-angular' into #63-upd…
lillykoller Nov 25, 2025
0ac6e00
feat(angular): updating to angular 21
lillykoller Nov 25, 2025
216cff7
feat(angular): updating to angular 21
lillykoller Nov 25, 2025
c7bbf5a
build(release): next version [skip_build]
actions-user Nov 25, 2025
73abe3e
feat(angular): migrating to signals
lillykoller Nov 25, 2025
958434f
Merge remote-tracking branch 'origin/#63-update-angular' into #63-upd…
lillykoller Nov 25, 2025
2a5ed87
feat(angular): migrating to signals
lillykoller Nov 25, 2025
074403a
build(release): next version [skip_build]
actions-user Nov 25, 2025
448d673
feat(angular): refactoring
lillykoller Nov 25, 2025
151a844
Merge remote-tracking branch 'origin/#63-update-angular' into #63-upd…
lillykoller Nov 25, 2025
2f2e4b3
feat(angular): refactoring
lillykoller Nov 25, 2025
371d118
build(release): next version [skip_build]
actions-user Nov 25, 2025
8aa6817
feat(angular): using signals
lillykoller Nov 25, 2025
276b948
feat(angular): using signals
lillykoller Nov 25, 2025
e2fbe6d
build(release): next version [skip_build]
actions-user Nov 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Shows the mapping between the angular version and our lib versions.

| Angular Version | Lib Version |
|-----------------|-------------------------------|
| `^21` | `^13` |
| `^20` | `^11 \|\| ^12` |
| `^19` | `^7 \|\| ^8 \|\| ^9 \|\| ^10` |
| `^18` | `^6` |
Expand Down
2 changes: 2 additions & 0 deletions apps/styleguide/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { provideZoneChangeDetection } from '@angular/core'
import { provideHttpClient } from '@angular/common/http'
import { bootstrapApplication } from '@angular/platform-browser'
import { provideAnimations } from '@angular/platform-browser/animations'
Expand All @@ -11,6 +12,7 @@ import { ROUTES } from './routes/routes.const'

bootstrapApplication(AppComponent, {
providers: [
provideZoneChangeDetection(),
provideHttpClient(),
provideAnimations(),
provideRouter(ROUTES),
Expand Down
7 changes: 2 additions & 5 deletions lerna.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
{
"useNx": false,
"packages": [
"libs/*",
"apps/*"
],
"version": "independent",
"packages": ["libs/*", "apps/*"],
"version": "13.0.0-pr63.5",
"command": {
"version": {
"allowBranch": "*",
Expand Down
16 changes: 8 additions & 8 deletions libs/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shiftcode/ngx-components",
"version": "12.0.2",
"version": "13.0.0-pr63.5",
"repository": "https://github.com/shiftcode/sc-ng-commons-public",
"license": "MIT",
"author": "shiftcode GmbH <[email protected]>",
Expand All @@ -18,14 +18,14 @@
"tslib": "^2.5.0"
},
"peerDependencies": {
"@angular/animations": "^20.0.0",
"@angular/cdk": "^20.0.0",
"@angular/common": "^20.0.0",
"@angular/core": "^20.0.0",
"@angular/forms": "^20.0.0",
"@angular/router": "^20.0.0",
"@angular/animations": "^21.0.0",
"@angular/cdk": "^21.0.0",
"@angular/common": "^21.0.0",
"@angular/core": "^21.0.0",
"@angular/forms": "^21.0.0",
"@angular/router": "^21.0.0",
"@shiftcode/logger": "^3.0.0",
"@shiftcode/ngx-core": "^12.0.0 || ^12.1.0-pr61",
"@shiftcode/ngx-core": "^13.0.0 || ^13.0.0-pr63",
"rxjs": "^6.5.3 || ^7.4.0"
}
}
8 changes: 4 additions & 4 deletions libs/components/src/lib/auto-focus/auto-focus.directive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AfterViewInit, Directive, ElementRef, inject, DOCUMENT } from '@angular/core'
import { afterNextRender, Directive, DOCUMENT, ElementRef, inject } from '@angular/core'
import { isInputElement, LoggerService } from '@shiftcode/ngx-core'
import { Logger } from '@shiftcode/logger'

Expand All @@ -12,13 +12,13 @@ import { Logger } from '@shiftcode/logger'
selector: '[scAutoFocus]',
standalone: true,
})
export class AutoFocusDirective implements AfterViewInit {
export class AutoFocusDirective {
readonly element: HTMLElement = inject(ElementRef).nativeElement
private readonly document: Document = inject(DOCUMENT)
private readonly logger: Logger = inject(LoggerService).getInstance('AutoFocusDirective')

ngAfterViewInit(): void {
this.focus()
constructor() {
afterNextRender(() => this.focus())
}

focus(): boolean {
Expand Down
5 changes: 2 additions & 3 deletions libs/components/src/lib/button/button.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, HostListener, Output } from '@angular/core'
import { Component, HostListener, output } from '@angular/core'

/**
* Standalone Button Component.
Expand All @@ -17,8 +17,7 @@ import { Component, EventEmitter, HostListener, Output } from '@angular/core'
},
})
export class ButtonComponent {
@Output()
readonly action = new EventEmitter<MouseEvent | KeyboardEvent>()
readonly action = output<MouseEvent | KeyboardEvent>()

@HostListener('keypress', ['$event'])
onKeypress(event: KeyboardEvent) {
Expand Down
27 changes: 11 additions & 16 deletions libs/components/src/lib/click-outside/click-outside.directive.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
import { Directive, ElementRef, EventEmitter, inject, Input, OnChanges, OnDestroy, Output } from '@angular/core'
import { Directive, effect, ElementRef, inject, input, output } from '@angular/core'
import { UIEventService } from '@shiftcode/ngx-core'
import { Subscription } from 'rxjs'

/**
* Standalone Directive to listen for 'outside' element clicks
*/
@Directive({ selector: '[scClickOutside]', standalone: true })
export class ClickOutsideDirective implements OnDestroy, OnChanges {
@Input('scClickOutsideDisabled') disabled = false
export class ClickOutsideDirective {
readonly disabled = input(false, { alias: 'scClickOutsideDisabled' })

@Output() readonly scClickOutside = new EventEmitter<Event>()
readonly scClickOutside = output<Event>()

private subscription?: Subscription
private readonly element: HTMLElement = inject(ElementRef).nativeElement
private readonly uiEventService = inject(UIEventService)

ngOnChanges(): void {
// as there is only one input, ngOnChanges is only called when `isActive` changes
this.subscription?.unsubscribe()
if (!this.disabled) {
this.subscription = this.uiEventService.forEvent(['click', 'touchstart']).subscribe(this.handleDocumentClick)
}
}

ngOnDestroy(): void {
this.subscription?.unsubscribe()
constructor() {
effect((onCleanup) => {
if (!this.disabled()) {
const sub = this.uiEventService.forEvent(['click', 'touchstart']).subscribe(this.handleDocumentClick)
onCleanup(() => sub.unsubscribe())
}
})
}

private handleDocumentClick = (event: Event) => {
Expand Down
28 changes: 13 additions & 15 deletions libs/components/src/lib/flying-focus/flying-focus.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { isPlatformServer } from '@angular/common'
import {
afterNextRender,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DOCUMENT,
ElementRef,
inject,
OnInit,
PLATFORM_ID,
DOCUMENT,
} from '@angular/core'
import { WindowRef } from '@shiftcode/ngx-core'
import { fromEvent } from 'rxjs'
Expand Down Expand Up @@ -35,7 +35,7 @@ const CRUCIAL_KEYS = ['Tab', 'Enter', 'Space', 'Escape', 'ArrowUp', 'ArrowRight'
styleUrls: ['./flying-focus.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FlyingFocusComponent implements OnInit {
export class FlyingFocusComponent {
readonly element: HTMLElement = inject(ElementRef).nativeElement

private keyDownTime: number
Expand All @@ -46,16 +46,7 @@ export class FlyingFocusComponent implements OnInit {
private readonly win: Window | null = inject(WindowRef).nativeWindow

constructor() {
// no need for cd cycles here.
inject(ChangeDetectorRef).detach()

const doc = inject(DOCUMENT)
this.docEl = doc.documentElement
this.bodyEl = doc.body
}

ngOnInit() {
if (this.isBrowser && this.win) {
afterNextRender(() => {
const opts: EventListenerOptions = { capture: true }

fromEvent<KeyboardEvent>(this.docEl, 'keydown', opts).subscribe(this.onKeydown)
Expand All @@ -64,8 +55,15 @@ export class FlyingFocusComponent implements OnInit {

fromEvent(this.docEl, 'mousedown', opts).subscribe(this.onMouseDown)

fromEvent(this.win, 'blur').subscribe(this.onWindowBlur)
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
fromEvent(this.win!, 'blur').subscribe(this.onWindowBlur)
})
// no need for cd cycles here.
inject(ChangeDetectorRef).detach()

const doc = inject(DOCUMENT)
this.docEl = doc.documentElement
this.bodyEl = doc.body
}

private readonly onKeydown = (event: KeyboardEvent) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Directive, Input, ViewContainerRef, ViewRef, inject } from '@angular/core'
import { Directive, ViewContainerRef, ViewRef, inject, input, effect } from '@angular/core'

/**
* Directive to insert a viewRef to the template.
Expand All @@ -15,14 +15,18 @@ import { Directive, Input, ViewContainerRef, ViewRef, inject } from '@angular/co
*/
@Directive({ selector: '[scInsertViewRef]', standalone: true })
export class InsertViewRefDirective {
readonly scInsertViewRef = input<ViewRef | null | undefined>()

private readonly container = inject(ViewContainerRef)

@Input()
set scInsertViewRef(val: ViewRef | null | undefined) {
this.container.clear()
if (val) {
this.insert(val)
}
constructor() {
effect(() => {
const val = this.scInsertViewRef()
this.container.clear()
if (val) {
this.insert(val)
}
})
}

get hasAttachedView(): boolean {
Expand Down
1 change: 1 addition & 0 deletions libs/components/src/lib/rx/rx-if.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface RxIfContext<T> {
* - renders the view in case of a truthy emitted value
* - renders the else template (if specified) as long as there is no value resolved or emitted, when a falsy value was emitted, or when not an observable/promise but null was provided
* - renders the error template/component (if specified or a default was set) in case of an error
* @Deprecated use Signals instead
* It's basically an alternative to the angular *ngIf Directive + the async pipe BUT does not trigger change detection cycles on every emission in the whole component but only inside the ViewRef Container of the directive itself.
* @example ```html
* <ng-template #noItemsTpl>No Items</ng-template>
Expand Down
1 change: 1 addition & 0 deletions libs/components/src/lib/rx/rx-let.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface RxLetContext<T> {
* - renders the suspense template/component (if specified or a default was set) as long as there is no value resolved or emitted
* - renders the error template/component (if specified or a default was set) in case of an error
* -> use `scRxLet` instead of `ngIf` + `async` pipe -> does not trigger CD on outer component when new value emitted
* @Deprecated use Signals instead
* @hint to disable the suspense template null can be provided
* @hint: as it does not trigger a CD on the outer component, potential View/Content Queries won't be updated
*
Expand Down
13 changes: 6 additions & 7 deletions libs/components/src/lib/smooth-height/smooth-height.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { animate, style, transition, trigger } from '@angular/animations'
import { Component, ElementRef, HostBinding, Input, OnChanges, inject } from '@angular/core'
import { Component, ElementRef, HostBinding, inject, input, afterNextRender } from '@angular/core'

/**
* Standalone Component to smoothly animate height changes.
Expand All @@ -20,20 +20,19 @@ import { Component, ElementRef, HostBinding, Input, OnChanges, inject } from '@a
]),
],
})
export class SmoothHeightComponent implements OnChanges {
@Input()
trigger: any
export class SmoothHeightComponent {
readonly trigger = input<any>()

private startHeight: number
private readonly element = inject(ElementRef)

@HostBinding('@grow')
get grow() {
return { value: this.trigger, params: { startHeight: this.startHeight } }
return { value: this.trigger(), params: { startHeight: this.startHeight } }
}

ngOnChanges() {
this.setStartHeight()
constructor() {
afterNextRender(() => this.setStartHeight())
}

private setStartHeight() {
Expand Down
74 changes: 33 additions & 41 deletions libs/components/src/lib/svg-animate/svg-animate.directive.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'
import { AfterViewInit, Directive, ElementRef, Input, OnChanges, OnInit, inject } from '@angular/core'
import { afterNextRender, booleanAttribute, Directive, effect, ElementRef, inject, input } from '@angular/core'

type SvgAnimationStates = Record<string, boolean>
/**
* Standalone Directive to animate SVG parts by calling beginElement method
* by default the initially active states are applied without animation (disable behaviour with `withInitAnimation`)
Expand All @@ -18,52 +18,44 @@ import { AfterViewInit, Directive, ElementRef, Input, OnChanges, OnInit, inject
* ```
*/
@Directive({ selector: '[scSvgAnimate]', standalone: true })
export class SvgAnimateDirective implements OnChanges, AfterViewInit, OnInit {
export class SvgAnimateDirective {
/**
* state input in form {selector:state} - will be animated when state === true
*/
@Input('scSvgAnimate')
states: Record<string, boolean> | null

@Input()
set withInitAnimation(value: BooleanInput) {
this._withInitAnimation = coerceBooleanProperty(value)
}

get withInitAnimation(): boolean {
return this._withInitAnimation
}

readonly element = inject(ElementRef).nativeElement
private _withInitAnimation = false

ngOnInit() {
if (!this.withInitAnimation) {
// set initial state without animation
this.getElementsToActivate().forEach((el) => {
const attr = el.getAttribute('attributeName')
const value = el.getAttribute('to')
if (attr && value !== null && el.parentElement) {
el.parentElement.setAttribute(attr, value)
}
})
}
}

ngOnChanges() {
this.apply()
}

ngAfterViewInit() {
this.apply()
readonly states = input(
{},
{ alias: 'scSvgAnimate', transform: (states: SvgAnimationStates | null | undefined) => states ?? {} },
)

readonly withInitAnimation = input(false, { transform: booleanAttribute })

readonly element: HTMLElement = inject(ElementRef).nativeElement

constructor() {
effect(() => {
this.apply(this.states())
})

afterNextRender(() => {
if (!this.withInitAnimation()) {
// set initial state without animation
this.getElementsToActivate(this.states()).forEach((el) => {
const attr = el.getAttribute('attributeName')
const value = el.getAttribute('to')
if (attr && value !== null && el.parentElement) {
el.parentElement.setAttribute(attr, value)
}
})
}
})
}

private apply() {
this.getElementsToActivate().forEach((el) => el.beginElement())
private apply(states: SvgAnimationStates) {
this.getElementsToActivate(states).forEach((el) => el.beginElement())
}

private getElementsToActivate(): SVGAnimateElement[] {
return Object.entries(this.states || {})
private getElementsToActivate(states: SvgAnimationStates): SVGAnimateElement[] {
return Object.entries(states || {})
.filter(([, state]) => !!state)
.map(([selector]) => this.element.querySelector(selector))
.filter((el): el is SVGAnimateElement & { beginElement: () => void } => !!el && 'beginElement' in el)
Expand Down
Loading