feat: Add brightness control functionality with signals
Add comprehensive brightness control feature with the following capabilities: - Input signals for configuration (enableBrightnessControl, brightnessStep, minBrightness, maxBrightness) - brightnessChanged output signal for reactive updates - brightnessIn() and brightnessOut() methods for adjusting brightness - resetBrightness() method to restore default value - UI controls positioned at bottom center alongside zoom controls - Keyboard accessibility (Enter/Space) and ARIA labels - Visual feedback with disabled states at min/max brightness - CSS brightness filter applied via reactive effect
This commit is contained in:
parent
a6524480d5
commit
41ef9989e2
3 changed files with 156 additions and 14 deletions
|
|
@ -2,16 +2,47 @@
|
|||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
||||
<!-- Control: one button -->
|
||||
@if (isControl()) {
|
||||
<div
|
||||
class="pz-zoom-button pz-zoom-control-position-bottom"
|
||||
[class.pz-zoom-button-out]="isZoomedIn()"
|
||||
(click)="toggleZoom()"
|
||||
(keydown.enter)="toggleZoom()"
|
||||
(keydown.space)="toggleZoom(); $event.preventDefault()"
|
||||
role="button"
|
||||
[attr.aria-label]="isZoomedIn() ? 'Zoom out' : 'Zoom in'"
|
||||
tabindex="0"
|
||||
></div>
|
||||
<!-- Controls container at bottom center -->
|
||||
@if (isControl() || isBrightnessControl()) {
|
||||
<div class="pz-controls-container">
|
||||
<!-- Zoom control -->
|
||||
@if (isControl()) {
|
||||
<div
|
||||
class="pz-zoom-button"
|
||||
[class.pz-zoom-button-out]="isZoomedIn()"
|
||||
(click)="toggleZoom()"
|
||||
(keydown.enter)="toggleZoom()"
|
||||
(keydown.space)="toggleZoom(); $event.preventDefault()"
|
||||
role="button"
|
||||
[attr.aria-label]="isZoomedIn() ? 'Zoom out' : 'Zoom in'"
|
||||
tabindex="0"
|
||||
></div>
|
||||
}
|
||||
|
||||
<!-- Brightness controls -->
|
||||
@if (isBrightnessControl()) {
|
||||
<div class="pz-brightness-controls">
|
||||
<div
|
||||
class="pz-brightness-button pz-brightness-decrease"
|
||||
[class.pz-brightness-button-disabled]="isBrightnessAtMin()"
|
||||
(click)="brightnessOut()"
|
||||
(keydown.enter)="brightnessOut()"
|
||||
(keydown.space)="brightnessOut(); $event.preventDefault()"
|
||||
role="button"
|
||||
aria-label="Decrease brightness"
|
||||
tabindex="0"
|
||||
></div>
|
||||
<div
|
||||
class="pz-brightness-button pz-brightness-increase"
|
||||
[class.pz-brightness-button-disabled]="isBrightnessAtMax()"
|
||||
(click)="brightnessIn()"
|
||||
(keydown.enter)="brightnessIn()"
|
||||
(keydown.space)="brightnessIn(); $event.preventDefault()"
|
||||
role="button"
|
||||
aria-label="Increase brightness"
|
||||
tabindex="0"
|
||||
></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,5 +97,54 @@
|
|||
.pz-zoom-in
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgc3R5bGU9IiI+PHJlY3QgaWQ9ImJhY2tncm91bmRyZWN0IiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4PSIwIiB5PSIwIiBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiLz48ZyBjbGFzcz0iY3VycmVudExheWVyIiBzdHlsZT0iIj48dGl0bGU+TGF5ZXIgMTwvdGl0bGU+PHBhdGggZD0iTTE5IDEzaC02djZoLTJ2LTZINXYtMmg2VjVoMnY2aDZ2MnoiIGlkPSJzdmdfMSIgY2xhc3M9IiIgc3Ryb2tlPSJub25lIiBmaWxsPSIjZmZmZmZmIiBmaWxsLW9wYWNpdHk9IjEiLz48cGF0aCBkPSJNLTE1LjgzNjczNDQyMDQ2MTY1Myw0NC41MzU0MDkzMDY3MTAxOCBoNTguMjA0MDgwODI3NTkzMDkgdi02LjU3NjIyNjcyMzM2OTIyMTUgSC0xNS44MzY3MzQ0MjA0NjE2NTMgeiIgZmlsbD0ibm9uZSIgaWQ9InN2Z18yIiBjbGFzcz0iIiBzdHJva2U9Im5vbmUiLz48L2c+PC9zdmc+)
|
||||
|
||||
.pz-zoom-out
|
||||
.pz-zoom-out
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHJlY3QgaWQ9ImJhY2tncm91bmRyZWN0IiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4PSIwIiB5PSIwIiBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiLz48ZyBjbGFzcz0iY3VycmVudExheWVyIiBzdHlsZT0iIj48dGl0bGU+TGF5ZXIgMTwvdGl0bGU+PHBhdGggZD0iTTE5IDEzSDV2LTJoMTR2MnoiIGlkPSJzdmdfMSIgY2xhc3M9IiIgZmlsbD0iI2ZmZmZmZiIgZmlsbC1vcGFjaXR5PSIxIi8+PC9nPjwvc3ZnPg==)
|
||||
|
||||
|
||||
/* Controls container */
|
||||
.pz-controls-container
|
||||
position: absolute
|
||||
bottom: 16px
|
||||
left: 50%
|
||||
transform: translateX(-50%)
|
||||
display: flex
|
||||
gap: 12px
|
||||
align-items: center
|
||||
z-index: 1000
|
||||
|
||||
|
||||
/* Brightness controls */
|
||||
.pz-brightness-controls
|
||||
display: flex
|
||||
gap: 8px
|
||||
align-items: center
|
||||
|
||||
.pz-brightness-button
|
||||
position: relative
|
||||
width: 48px
|
||||
height: 48px
|
||||
background-color: rgba(0, 0, 0, .8)
|
||||
background-position: center
|
||||
background-repeat: no-repeat
|
||||
background-size: 24px
|
||||
border-radius: 4px
|
||||
opacity: 0.5
|
||||
cursor: pointer
|
||||
transition: opacity .1s
|
||||
user-select: none
|
||||
|
||||
.pz-brightness-button:hover
|
||||
opacity: 0.7
|
||||
|
||||
.pz-brightness-button-disabled
|
||||
opacity: 0.3
|
||||
cursor: not-allowed
|
||||
|
||||
.pz-brightness-button-disabled:hover
|
||||
opacity: 0.3
|
||||
|
||||
.pz-brightness-increase
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0yMCA4LjY5VjRoLTQuNjlMMTIgLjY5IDguNjkgNEg0djQuNjlMLjY5IDEyIDQgMTUuMzFWMjBoNC42OUwxMiAyMy4zMSAxNS4zMSAyMEgyMHYtNC42OUwyMy4zMSAxMiAyMCA4LjY5ek0xMiAxOGMtMy4zMSAwLTYtMi42OS02LTZzMi42OS02IDYtNiA2IDIuNjkgNiA2LTIuNjkgNi02IDZ6bTAtMTBjLTIuMjEgMC00IDEuNzktNCA0czEuNzkgNCA0IDQgNC0xLjc5IDQtNC0xLjc5LTQtNC00eiIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg==)
|
||||
|
||||
.pz-brightness-decrease
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0yMCA4LjY5VjRoLTQuNjlMMTIgLjY5IDguNjkgNEg0djQuNjlMLjY5IDEyIDQgMTUuMzFWMjBoNC42OUwxMiAyMy4zMSAxNS4zMSAyMEgyMHYtNC42OUwyMy4zMSAxMiAyMCA4LjY5ek0xMiAxOGMtMy4zMSAwLTYtMi42OS02LTZzMi42OS02IDYtNiA2IDIuNjkgNiA2LTIuNjkgNi02IDZ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+)
|
||||
|
|
|
|||
|
|
@ -66,11 +66,19 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
|
|||
draggableImage = input<boolean>(defaultProperties.draggableImage!);
|
||||
draggableOnPinch = input<boolean>(defaultProperties.draggableOnPinch!);
|
||||
|
||||
// Output signal
|
||||
// Brightness control inputs
|
||||
enableBrightnessControl = input<boolean>(false);
|
||||
brightnessStep = input<number>(0.1);
|
||||
minBrightness = input<number>(0.1);
|
||||
maxBrightness = input<number>(2.0);
|
||||
|
||||
// Output signals
|
||||
zoomChanged = output<number>();
|
||||
brightnessChanged = output<number>();
|
||||
|
||||
// Internal signals
|
||||
private currentScale = signal<number>(1);
|
||||
private currentBrightness = signal<number>(1.0);
|
||||
|
||||
// Computed signals for reactive properties
|
||||
mergedProperties = computed<ComponentProperties>(() => {
|
||||
|
|
@ -173,6 +181,26 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
|
|||
return true;
|
||||
});
|
||||
|
||||
// Brightness computed signals
|
||||
brightness = computed<number>(() => {
|
||||
return this.currentBrightness();
|
||||
});
|
||||
|
||||
isBrightnessControl = computed<boolean>(() => {
|
||||
if (this.isDisabled()) {
|
||||
return false;
|
||||
}
|
||||
return this.enableBrightnessControl();
|
||||
});
|
||||
|
||||
isBrightnessAtMin = computed<boolean>(() => {
|
||||
return this.brightness() <= this.minBrightness();
|
||||
});
|
||||
|
||||
isBrightnessAtMax = computed<boolean>(() => {
|
||||
return this.brightness() >= this.maxBrightness();
|
||||
});
|
||||
|
||||
constructor() {
|
||||
this.defaultComponentProperties = this.getDefaultComponentProperties();
|
||||
|
||||
|
|
@ -184,6 +212,15 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
|
|||
this.detectLimitZoom();
|
||||
}
|
||||
});
|
||||
|
||||
// Effect to apply brightness filter
|
||||
effect(() => {
|
||||
const brightness = this.brightness();
|
||||
const element = this.elementRef.nativeElement.querySelector('.pinch-zoom-content') as HTMLElement;
|
||||
if (element) {
|
||||
element.style.filter = `brightness(${brightness})`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
@ -231,6 +268,31 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
|
|||
return this.pinchZoom?.zoomOut(value) || this.scale();
|
||||
}
|
||||
|
||||
brightnessIn(): number {
|
||||
const newBrightness = Math.min(
|
||||
this.brightness() + this.brightnessStep(),
|
||||
this.maxBrightness()
|
||||
);
|
||||
this.currentBrightness.set(newBrightness);
|
||||
this.brightnessChanged.emit(newBrightness);
|
||||
return newBrightness;
|
||||
}
|
||||
|
||||
brightnessOut(): number {
|
||||
const newBrightness = Math.max(
|
||||
this.brightness() - this.brightnessStep(),
|
||||
this.minBrightness()
|
||||
);
|
||||
this.currentBrightness.set(newBrightness);
|
||||
this.brightnessChanged.emit(newBrightness);
|
||||
return newBrightness;
|
||||
}
|
||||
|
||||
resetBrightness(): void {
|
||||
this.currentBrightness.set(1.0);
|
||||
this.brightnessChanged.emit(1.0);
|
||||
}
|
||||
|
||||
detectLimitZoom(): void {
|
||||
this.pinchZoom?.detectLimitZoom();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue