Skip to content

Commit f2219e8

Browse files
authored
refactor: drop signal writes effect (#455)
1 parent f695830 commit f2219e8

File tree

4 files changed

+132
-106
lines changed

4 files changed

+132
-106
lines changed

projects/angular-split/src/lib/split-area/split-area.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ export class SplitAreaComponent {
4747
return 0
4848
}
4949

50-
const size = this.size()
51-
// auto acts the same as * in all calculations
52-
return size === 'auto' ? '*' : size
50+
const visibleIndex = this.split._visibleAreas().findIndex((area) => area === this)
51+
52+
return this.split._alignedVisibleAreasSizes()[visibleIndex]
5353
}),
5454
)
5555
/**

projects/angular-split/src/lib/split/split.component.ts

Lines changed: 112 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import {
1313
effect,
1414
inject,
1515
input,
16+
isDevMode,
1617
output,
1718
signal,
18-
untracked,
1919
} from '@angular/core'
2020
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
2121
import type { SplitAreaComponent } from '../split-area/split-area.component'
@@ -31,6 +31,7 @@ import {
3131
numberAttributeWithFallback,
3232
sum,
3333
toRecord,
34+
assertUnreachable,
3435
} from '../utils'
3536
import { DOCUMENT, NgStyle, NgTemplateOutlet } from '@angular/common'
3637
import { SplitGutterInteractionEvent, SplitAreaSize } from '../models'
@@ -115,56 +116,11 @@ export class SplitComponent {
115116

116117
readonly dragProgress$ = this.dragProgressSubject.asObservable()
117118

118-
private readonly visibleAreas = computed(() => this._areas().filter((area) => area.visible()))
119-
private readonly gridTemplateColumnsStyle = computed(() => {
120-
const columns: string[] = []
121-
const sumNonWildcardSizes = sum(this.visibleAreas(), (area) => {
122-
const size = area._internalSize()
123-
return size === '*' ? 0 : size
124-
})
125-
const visibleAreasCount = this.visibleAreas().length
126-
127-
let visitedVisibleAreas = 0
128-
129-
this._areas().forEach((area, index, areas) => {
130-
const unit = this.unit()
131-
const areaSize = area._internalSize()
132-
133-
// Add area size column
134-
if (!area.visible()) {
135-
columns.push(unit === 'percent' || areaSize === '*' ? '0fr' : '0px')
136-
} else {
137-
if (unit === 'pixel') {
138-
const columnValue = areaSize === '*' ? '1fr' : `${areaSize}px`
139-
columns.push(columnValue)
140-
} else {
141-
const percentSize = areaSize === '*' ? 100 - sumNonWildcardSizes : areaSize
142-
const columnValue = `${percentSize}fr`
143-
columns.push(columnValue)
144-
}
145-
146-
visitedVisibleAreas++
147-
}
148-
149-
const isLastArea = index === areas.length - 1
150-
151-
if (isLastArea) {
152-
return
153-
}
154-
155-
const remainingVisibleAreas = visibleAreasCount - visitedVisibleAreas
156-
157-
// Only add gutter with size if this area is visible and there are more visible areas after this one
158-
// to avoid ghost gutters
159-
if (area.visible() && remainingVisibleAreas > 0) {
160-
columns.push(`${this.gutterSize()}px`)
161-
} else {
162-
columns.push('0px')
163-
}
164-
})
165-
166-
return this.direction() === 'horizontal' ? `1fr / ${columns.join(' ')}` : `${columns.join(' ')} / 1fr`
167-
})
119+
/**
120+
* @internal
121+
*/
122+
readonly _visibleAreas = computed(() => this._areas().filter((area) => area.visible()))
123+
private readonly gridTemplateColumnsStyle = computed(() => this.createGridTemplateColumnsStyle())
168124
private readonly hostClasses = computed(() =>
169125
createClassesString({
170126
[`as-${this.direction()}`]: true,
@@ -179,6 +135,11 @@ export class SplitComponent {
179135
* @internal
180136
*/
181137
readonly _isDragging = computed(() => this.draggedGutterIndex() !== undefined)
138+
/**
139+
* @internal
140+
* Should only be used by {@link SplitAreaComponent._internalSize}
141+
*/
142+
readonly _alignedVisibleAreasSizes = computed(() => this.createAlignedVisibleAreasSize())
182143

183144
@HostBinding('class') protected get hostClassesBinding() {
184145
return this.hostClasses()
@@ -189,45 +150,17 @@ export class SplitComponent {
189150
}
190151

191152
constructor() {
192-
effect(
193-
() => {
194-
const visibleAreas = this.visibleAreas()
195-
const unit = this.unit()
196-
const isInAutoMode = visibleAreas.every((area) => area.size() === 'auto')
197-
198-
untracked(() => {
199-
// Special mode when no size input was declared which is a valid mode
200-
if (unit === 'percent' && visibleAreas.length > 1 && isInAutoMode) {
201-
visibleAreas.forEach((area) => area._internalSize.set(100 / visibleAreas.length))
202-
return
203-
}
204-
205-
visibleAreas.forEach((area) => area._internalSize.reset())
206-
207-
const isValid = areAreasValid(visibleAreas, unit)
208-
209-
if (isValid) {
210-
return
211-
}
153+
if (isDevMode()) {
154+
// Logs warnings to console when the provided areas sizes are invalid
155+
effect(() => {
156+
// Special mode when no size input was declared which is a valid mode
157+
if (this.unit() === 'percent' && this._visibleAreas().every((area) => area.size() === 'auto')) {
158+
return
159+
}
212160

213-
if (unit === 'percent') {
214-
// Distribute sizes equally
215-
const defaultSize = 100 / visibleAreas.length
216-
visibleAreas.forEach((area) => area._internalSize.set(defaultSize))
217-
} else if (unit === 'pixel') {
218-
const wildcardAreas = visibleAreas.filter((area) => area._internalSize() === '*')
219-
220-
// Make sure only one wildcard area
221-
if (wildcardAreas.length === 0) {
222-
visibleAreas[0]._internalSize.set('*')
223-
} else if (wildcardAreas.length > 1) {
224-
wildcardAreas.filter((_, i) => i !== 0).forEach((area) => area._internalSize.set(100))
225-
}
226-
}
227-
})
228-
},
229-
{ allowSignalWrites: true },
230-
)
161+
areAreasValid(this._visibleAreas(), this.unit(), true)
162+
})
163+
}
231164

232165
// Responsible for updating grid template style. Must be this way and not based on HostBinding
233166
// as change detection for host binding is bound to the parent component and this style
@@ -450,7 +383,7 @@ export class SplitComponent {
450383
}
451384

452385
private createAreaSizes() {
453-
return this.visibleAreas().map((area) => area._internalSize())
386+
return this._visibleAreas().map((area) => area._internalSize())
454387
}
455388

456389
private createDragStartContext(
@@ -460,7 +393,7 @@ export class SplitComponent {
460393
): DragStartContext {
461394
const splitBoundingRect = this.elementRef.nativeElement.getBoundingClientRect()
462395
const splitSize = this.direction() === 'horizontal' ? splitBoundingRect.width : splitBoundingRect.height
463-
const totalAreasPixelSize = splitSize - (this.visibleAreas().length - 1) * this.gutterSize()
396+
const totalAreasPixelSize = splitSize - (this._visibleAreas().length - 1) * this.gutterSize()
464397
// Use the internal size and split size to calculate the pixel size from wildcard and percent areas
465398
const areaPixelSizesWithWildcard = this._areas().map((area) => {
466399
if (this.unit() === 'pixel') {
@@ -598,4 +531,92 @@ export class SplitComponent {
598531

599532
this.dragProgressSubject.next(this.createDragInteractionEvent(this.draggedGutterIndex()))
600533
}
534+
535+
private createGridTemplateColumnsStyle(): string {
536+
const columns: string[] = []
537+
const sumNonWildcardSizes = sum(this._visibleAreas(), (area) => {
538+
const size = area._internalSize()
539+
return size === '*' ? 0 : size
540+
})
541+
const visibleAreasCount = this._visibleAreas().length
542+
543+
let visitedVisibleAreas = 0
544+
545+
this._areas().forEach((area, index, areas) => {
546+
const unit = this.unit()
547+
const areaSize = area._internalSize()
548+
549+
// Add area size column
550+
if (!area.visible()) {
551+
columns.push(unit === 'percent' || areaSize === '*' ? '0fr' : '0px')
552+
} else {
553+
if (unit === 'pixel') {
554+
const columnValue = areaSize === '*' ? '1fr' : `${areaSize}px`
555+
columns.push(columnValue)
556+
} else {
557+
const percentSize = areaSize === '*' ? 100 - sumNonWildcardSizes : areaSize
558+
const columnValue = `${percentSize}fr`
559+
columns.push(columnValue)
560+
}
561+
562+
visitedVisibleAreas++
563+
}
564+
565+
const isLastArea = index === areas.length - 1
566+
567+
if (isLastArea) {
568+
return
569+
}
570+
571+
const remainingVisibleAreas = visibleAreasCount - visitedVisibleAreas
572+
573+
// Only add gutter with size if this area is visible and there are more visible areas after this one
574+
// to avoid ghost gutters
575+
if (area.visible() && remainingVisibleAreas > 0) {
576+
columns.push(`${this.gutterSize()}px`)
577+
} else {
578+
columns.push('0px')
579+
}
580+
})
581+
582+
return this.direction() === 'horizontal' ? `1fr / ${columns.join(' ')}` : `${columns.join(' ')} / 1fr`
583+
}
584+
585+
private createAlignedVisibleAreasSize(): SplitAreaSize[] {
586+
const visibleAreasSizes = this._visibleAreas().map((area): SplitAreaSize => {
587+
const size = area.size()
588+
return size === 'auto' ? '*' : size
589+
})
590+
const isValid = areAreasValid(this._visibleAreas(), this.unit(), false)
591+
592+
if (isValid) {
593+
return visibleAreasSizes
594+
}
595+
596+
const unit = this.unit()
597+
598+
if (unit === 'percent') {
599+
// Distribute sizes equally
600+
const defaultPercentSize = 100 / visibleAreasSizes.length
601+
return visibleAreasSizes.map(() => defaultPercentSize)
602+
}
603+
604+
if (unit === 'pixel') {
605+
// Make sure only one wildcard area
606+
const wildcardAreas = visibleAreasSizes.filter((areaSize) => areaSize === '*')
607+
608+
if (wildcardAreas.length === 0) {
609+
return ['*', ...visibleAreasSizes.slice(1)]
610+
} else {
611+
const firstWildcardIndex = visibleAreasSizes.findIndex((areaSize) => areaSize === '*')
612+
const defaultPxSize = 100
613+
614+
return visibleAreasSizes.map((areaSize, index) =>
615+
index === firstWildcardIndex || areaSize !== '*' ? areaSize : defaultPxSize,
616+
)
617+
}
618+
}
619+
620+
return assertUnreachable(unit, 'SplitUnit')
621+
}
601622
}

projects/angular-split/src/lib/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,7 @@ export function leaveNgZone<T>() {
131131
}
132132

133133
export const numberAttributeWithFallback = (fallback: number) => (value: unknown) => numberAttribute(value, fallback)
134+
135+
export const assertUnreachable = (value: never, name: string) => {
136+
throw new Error(`as-split: unknown value "${value}" for "${name}"`)
137+
}

projects/angular-split/src/lib/validations.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import { isDevMode } from '@angular/core'
2-
import { SplitUnit } from './models'
1+
import { SplitAreaSize, SplitUnit } from './models'
32
import { SplitAreaComponent } from './split-area/split-area.component'
43
import { sum } from './utils'
54

6-
export function areAreasValid(areas: readonly SplitAreaComponent[], unit: SplitUnit): boolean {
5+
export function areAreasValid(areas: readonly SplitAreaComponent[], unit: SplitUnit, logWarnings: boolean): boolean {
76
if (areas.length === 0) {
87
return true
98
}
109

11-
const wildcardAreas = areas.filter((area) => area._internalSize() === '*')
10+
const areaSizes = areas.map((area): SplitAreaSize => {
11+
const size = area.size()
12+
return size === 'auto' ? '*' : size
13+
})
14+
15+
const wildcardAreas = areaSizes.filter((areaSize) => areaSize === '*')
1216

1317
if (wildcardAreas.length > 1) {
14-
if (isDevMode()) {
18+
if (logWarnings) {
1519
console.warn('as-split: Maximum one * area is allowed')
1620
}
1721

@@ -23,33 +27,30 @@ export function areAreasValid(areas: readonly SplitAreaComponent[], unit: SplitU
2327
return true
2428
}
2529

26-
if (isDevMode()) {
30+
if (logWarnings) {
2731
console.warn('as-split: Pixel mode must have exactly one * area')
2832
}
2933

3034
return false
3135
}
3236

33-
const sumPercent = sum(areas, (area) => {
34-
const size = area._internalSize()
35-
return size === '*' ? 0 : size
36-
})
37+
const sumPercent = sum(areaSizes, (areaSize) => (areaSize === '*' ? 0 : areaSize))
3738

3839
// As percent calculation isn't perfect we allow for a small margin of error
3940
if (wildcardAreas.length === 1) {
4041
if (sumPercent <= 100.1) {
4142
return true
4243
}
4344

44-
if (isDevMode()) {
45+
if (logWarnings) {
4546
console.warn(`as-split: Percent areas must total 100%`)
4647
}
4748

4849
return false
4950
}
5051

5152
if (sumPercent < 99.9 || sumPercent > 100.1) {
52-
if (isDevMode()) {
53+
if (logWarnings) {
5354
console.warn('as-split: Percent areas must total 100%')
5455
}
5556

0 commit comments

Comments
 (0)