diff --git a/projects/demo/src/app/demo/demo.component.html b/projects/demo/src/app/demo/demo.component.html index 7daf431..1191fc5 100644 --- a/projects/demo/src/app/demo/demo.component.html +++ b/projects/demo/src/app/demo/demo.component.html @@ -3,7 +3,8 @@
-
+ +
diff --git a/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.html b/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.html index 19e1432..95f2c23 100644 --- a/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.html +++ b/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.html @@ -25,7 +25,7 @@
-
+
@@ -77,13 +77,19 @@
-
-
- × - -
{{getWarningToShow()}}
+ +
+
+
+
+
+ × + +
{{getWarningToShow()}}
+
+ diff --git a/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.scss b/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.scss index e3fb481..3c104dc 100644 --- a/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.scss +++ b/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.scss @@ -121,6 +121,7 @@ .caption { font-weight: bold; flex: 0 0 auto; + overflow: hidden; &.percentageSpacing { flex: 40 1 0px; &.d30-70 { @@ -179,6 +180,16 @@ $triangleSize: 8px; klp-form-warning-icon { cursor: pointer; } + .absoluteAnchor { + position: absolute; + } + .fixedAnchor { + position: fixed; + } + // using a fixed wrapper here to avoid the overflow hidden properties of a parent container, which could cut off the message tooltip + .fixedWrapper { + position: fixed; + } } .errorTooltipTriangle { display: none; @@ -212,7 +223,7 @@ $triangleSize: 8px; } position: absolute; top: -0.6rem; - right: -$spacing-small; + right: -$spacing-large; transform: translateY(-100%); width: 20rem; diff --git a/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.ts b/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.ts index 6eec28e..ca8efc2 100644 --- a/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.ts +++ b/projects/klippa/ngx-enhancy-forms/src/lib/form/form-element/form-element.component.ts @@ -16,6 +16,7 @@ import {CustomErrorMessages, FormErrorMessages} from '../../types'; import {isValueSet, stringIsSetAndFilled} from '../../util/values'; import {FormComponent} from '../form.component'; import {awaitableForNextCycle} from '../../util/angular'; +import {getAllLimitingContainers} from '../../util/dom'; export const FORM_ERROR_MESSAGES = new InjectionToken('form.error.messages'); @@ -49,6 +50,9 @@ export class FormElementComponent implements AfterViewInit { @ViewChild('internalComponentRef') public internalComponentRef: ElementRef; @ViewChild('tailTpl') public tailTpl: TemplateRef; @ViewChild('captionDummyForSpaceCalculation') public captionDummyForSpaceCalculation: ElementRef; + @ViewChild('absoluteAnchor') public absoluteAnchor: ElementRef; + @ViewChild('fixedAnchor') public fixedAnchor: ElementRef; + @ViewChild('fixedWrapper') public fixedWrapper: ElementRef; @ContentChild(NG_VALUE_ACCESSOR) fieldInput: ValueAccessorBase; public captionRef: TemplateRef; @@ -61,6 +65,7 @@ export class FormElementComponent implements AfterViewInit { constructor( @Optional() private parent: FormComponent, @Inject(FORM_ERROR_MESSAGES) @Optional() private customMessages: CustomErrorMessages, + private elRef: ElementRef, ) { } @@ -70,6 +75,8 @@ export class FormElementComponent implements AfterViewInit { this.fieldInput?.onTouch.asObservable().subscribe((e) => { this.determinePopupState(); }); + + [...getAllLimitingContainers(this.elRef.nativeElement), window].forEach(e => e.addEventListener('scroll', this.setErrorTooltipOffset)); } public shouldShowErrorMessages(): boolean { @@ -136,7 +143,7 @@ export class FormElementComponent implements AfterViewInit { if (this.attachedControl?.touched !== true) { return null; } - if (!this.attachedControl?.errors) { + if (!this.attachedControl?.errors) { return null; } return firstError; @@ -161,7 +168,7 @@ export class FormElementComponent implements AfterViewInit { } } - scrollTo(): void{ + scrollTo(): void { this.internalComponentRef.nativeElement.scrollIntoView(true); // to give some breathing room, we scroll 100px more to the top this.getScrollableParent(this.internalComponentRef.nativeElement)?.scrollBy(0, -100); @@ -237,4 +244,14 @@ export class FormElementComponent implements AfterViewInit { this.popupState = 'lockedOpen'; } } + + public setErrorTooltipOffset = (): void => { + if (this.popupState !== 'lockedOpen' && this.popupState !== 'onHover') { + return; + } + const popupOffsetY = this.absoluteAnchor?.nativeElement.getBoundingClientRect().top - this.fixedAnchor?.nativeElement.getBoundingClientRect().top; + if (this.fixedWrapper?.nativeElement) { + this.fixedWrapper.nativeElement.style.transform = `translateY(${popupOffsetY}px)`; + } + }; } diff --git a/projects/klippa/ngx-enhancy-forms/src/lib/util/dom.ts b/projects/klippa/ngx-enhancy-forms/src/lib/util/dom.ts new file mode 100644 index 0000000..c7f26af --- /dev/null +++ b/projects/klippa/ngx-enhancy-forms/src/lib/util/dom.ts @@ -0,0 +1,15 @@ +export function getAllLimitingContainers(element: Element): Array { + const result = []; + let current = element; + while (current.parentElement) { + if (isLimitingContainer(current.parentElement)) { + result.push(current.parentElement); + } + current = current.parentElement; + } + return result; +} + +export function isLimitingContainer(element: Element): boolean { + return element.scrollHeight > element.clientHeight; +}