Fix date picker overflows in work package table, add auto alignment option for spot-drop-modal

feature/42358-standardise-date-pickers-drop-modal-portal
Benjamin Bädorf 2 years ago
parent 8d9cf86115
commit 1ad534b8e9
No known key found for this signature in database
GPG Key ID: 069CA2D117AB5CCF
  1. 4
      frontend/src/app/features/work-packages/components/wp-fast-table/builders/cell-builder.ts
  2. 2
      frontend/src/app/shared/components/fields/edit/edit-field.initializer.ts
  3. 6
      frontend/src/app/shared/components/fields/edit/field-types/date-picker-edit-field.component.ts
  4. 24
      frontend/src/app/shared/components/fields/edit/field-types/days-duration-edit-field.component.html
  5. 30
      frontend/src/app/shared/components/fields/edit/field-types/days-duration-edit-field.component.ts
  6. 2
      frontend/src/app/shared/components/fields/openproject-fields.module.ts
  7. 2
      frontend/src/app/spot/components/drop-modal/drop-modal.component.html
  8. 74
      frontend/src/app/spot/components/drop-modal/drop-modal.component.ts
  9. 6
      frontend/src/global_styles/content/work_packages/inplace_editing/_display_fields.sass

@ -28,6 +28,10 @@ export class CellBuilder {
td.classList.add('-max');
}
if ([ 'startDate', 'dueDate', 'duration' ].indexOf(attribute) !== -1) {
td.classList.add('-no-ellipsis');
}
const schema = this.schemaCache.of(workPackage).ofProperty(attribute);
if (schema && schema.type === 'User') {
td.classList.add('-contains-avatar');

@ -47,7 +47,7 @@ import { WorkPackageCommentFieldComponent } from 'core-app/features/work-package
import { ProjectEditFieldComponent } from './field-types/project-edit-field.component';
import { HoursDurationEditFieldComponent } from 'core-app/shared/components/fields/edit/field-types/hours-duration-edit-field.component';
import { UserEditFieldComponent } from './field-types/user-edit-field.component';
import { DaysDurationEditFieldComponent } from 'core-app/shared/components/fields/edit/field-types/days-duration-edit-field.compontent';
import { DaysDurationEditFieldComponent } from 'core-app/shared/components/fields/edit/field-types/days-duration-edit-field.component';
export function initializeCoreEditFields(editFieldService:EditFieldService, selectAutocompleterRegisterService:SelectAutocompleterRegisterService) {
return ():void => {

@ -36,8 +36,6 @@ import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decora
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { EditFieldComponent } from 'core-app/shared/components/fields/edit/edit-field.component';
import { DeviceService } from 'core-app/core/browser/device.service';
import { DateModalSchedulingService } from 'core-app/shared/components/datepicker/services/date-modal-scheduling.service';
import { DateModalRelationsService } from 'core-app/shared/components/datepicker/services/date-modal-relations.service';
@Directive()
export abstract class DatePickerEditFieldComponent extends EditFieldComponent implements OnInit, OnDestroy {
@ -45,10 +43,6 @@ export abstract class DatePickerEditFieldComponent extends EditFieldComponent im
@InjectField() deviceService:DeviceService;
@InjectField() dateModalScheduling:DateModalSchedulingService;
@InjectField() dateModalRelations:DateModalRelationsService;
@InjectField() injector:Injector;
ngOnInit():void {

@ -0,0 +1,24 @@
<spot-drop-modal
[open]="opened"
(closed)="cancel()"
alignment="bottom-center"
>
<input
type="number"
slot="trigger"
class="inline-edit--field op-input"
[ngModel]="formattedValue"
(click)="onInputClick($event)"
(focus)="opened = true"
disabled="disabled"
[id]="handler.htmlId"
/>
<op-wp-multi-date-form
[changeset]="change"
[fieldName]="name"
(save)="save()"
(cancel)="cancel()"
slot="body"
></op-wp-multi-date-form>
</spot-drop-modal>

@ -33,17 +33,33 @@ import {
import { DatePickerEditFieldComponent } from 'core-app/shared/components/fields/edit/field-types/date-picker-edit-field.component';
@Component({
template: `
<input type="number"
class="inline-edit--field op-input"
[ngModel]="formattedValue"
disabled="disabled"
[id]="handler.htmlId" />
`,
templateUrl: './days-duration-edit-field.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DaysDurationEditFieldComponent extends DatePickerEditFieldComponent {
opened = false;
public get formattedValue():number {
return Number(moment.duration(this.value).asDays().toFixed(0));
}
ngOnInit():void {
super.ngOnInit();
}
onInputClick(event:MouseEvent) {
event.stopPropagation();
}
showDatePickerModal() {
this.opened = true;
}
save() {
this.onModalClosed();
}
cancel():void {
this.handler.reset();
}
}

@ -63,7 +63,7 @@ import { EditFieldControlsModule } from 'core-app/shared/components/fields/edit/
import { ProjectEditFieldComponent } from './edit/field-types/project-edit-field.component';
import { HoursDurationEditFieldComponent } from 'core-app/shared/components/fields/edit/field-types/hours-duration-edit-field.component';
import { UserEditFieldComponent } from './edit/field-types/user-edit-field.component';
import { DaysDurationEditFieldComponent } from 'core-app/shared/components/fields/edit/field-types/days-duration-edit-field.compontent';
import { DaysDurationEditFieldComponent } from 'core-app/shared/components/fields/edit/field-types/days-duration-edit-field.component';
import { CombinedDateEditFieldComponent } from './edit/field-types/combined-date-edit-field.component';
@NgModule({

@ -7,6 +7,8 @@
cdkTrapFocus
tabindex="0"
#modalBody
>
<ng-content select="[slot=body]"></ng-content>

@ -1,5 +1,6 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
@ -7,11 +8,26 @@ import {
Input,
OnDestroy,
Output,
ViewChild,
} from '@angular/core';
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import SpotDropAlignmentOption from '../../drop-alignment-options';
import { findAllFocusableElementsWithin } from 'core-app/shared/helpers/focus-helpers';
import SpotDropAlignmentOption from '../../drop-alignment-options';
const findClippingParent = (el:HTMLElement):HTMLElement => {
const parent = el.parentElement;
if(!parent) {
return document.body;
}
const styles = window.getComputedStyle(parent);
if (styles.overflowY !== 'visible' || styles.overflowX !== 'visible') {
return parent;
}
return findClippingParent(parent);
}
@Component({
selector: 'spot-drop-modal',
@ -22,13 +38,20 @@ export class SpotDropModalComponent implements OnDestroy {
@HostBinding('class.spot-drop-modal') public className = true;
/**
* The alignment of the drop modal. There are twelve alignments in total. You can check which ones they are
* Whether to allow automatic changing the alignment based on the available space.
*/
@Input() public allowRepositioning:boolean = true;
/**
* The default alignment of the drop modal. There are twelve alignments in total. You can check which ones they are
* from the `SpotDropAlignmentOption` Enum that is available in 'core-app/spot/drop-alignment-options'.
*/
@Input() public alignment:SpotDropAlignmentOption = SpotDropAlignmentOption.BottomLeft;
private calculatedAlignment = SpotDropAlignmentOption.BottomLeft;
get alignmentClass():string {
return `spot-drop-modal--body_${this.alignment}`;
return `spot-drop-modal--body_${this.allowRepositioning ? this.calculatedAlignment : this.alignment}`;
}
public _open = false;
@ -58,6 +81,10 @@ export class SpotDropModalComponent implements OnDestroy {
window.addEventListener('orientationchange', this.appHeightListener);
this.appHeightListener();
if (this.allowRepositioning) {
this.recalculateAlignment();
}
const focusCatcherContainer = document.querySelectorAll("[data-modal-focus-catcher-container='true']")[0];
if (focusCatcherContainer) {
(findAllFocusableElementsWithin(focusCatcherContainer as HTMLElement)[0] as HTMLElement).focus();
@ -71,8 +98,6 @@ export class SpotDropModalComponent implements OnDestroy {
document.body.removeEventListener('keydown', this.escapeListener);
window.removeEventListener('resize', this.appHeightListener);
window.removeEventListener('orientationchange', this.appHeightListener);
console.log('close?', value);
this.closed.emit();
}
@ -106,21 +131,58 @@ export class SpotDropModalComponent implements OnDestroy {
close: this.i18n.t('js.spot.drop_modal.close'),
};
@ViewChild('modalBody') modalBody:ElementRef;
constructor(
readonly i18n:I18nService,
readonly elementRef:ElementRef,
readonly cdRef:ChangeDetectorRef,
) {}
close():void {
this.open = false;
}
private closeEventListener = this.close.bind(this) as () => void;
onBodyClick(e:MouseEvent):void {
// We stop propagation here so that clicks inside the body do not
// close the modal when the event reaches the document body
e.stopPropagation();
}
private recalculateAlignment(): void {
const clippingParent = findClippingParent(this.elementRef.nativeElement);
const parentRect = clippingParent.getBoundingClientRect();
const alignments = Object.values(SpotDropAlignmentOption) as SpotDropAlignmentOption[];
const index = alignments.indexOf(this.alignment);
const possibleAlignments = [
...alignments.splice(index),
...alignments.splice(0, index),
].filter((alignment:SpotDropAlignmentOption) => {
this.modalBody.nativeElement.classList.remove(this.alignmentClass);
this.calculatedAlignment = alignment;
this.modalBody.nativeElement.classList.add(this.alignmentClass);
const rect = this.modalBody.nativeElement.getBoundingClientRect();
const spaceOnLeft = parentRect.left <= rect.left;
const spaceOnRight = parentRect.right >= rect.right;
const spaceOnTop = parentRect.top <= rect.top;
const spaceOnBottom = parentRect.bottom >= rect.bottom;
return spaceOnLeft && spaceOnRight && spaceOnTop && spaceOnBottom;
});
if (possibleAlignments.length) {
this.calculatedAlignment = possibleAlignments[0];
} else {
this.calculatedAlignment = this.alignment;
}
this.cdRef.markForCheck();
}
ngOnDestroy():void {
document.body.removeEventListener('click', this.closeEventListener);
document.body.removeEventListener('keydown', this.escapeListener);
@ -128,8 +190,6 @@ export class SpotDropModalComponent implements OnDestroy {
window.removeEventListener('orientationchange', this.appHeightListener);
}
private closeEventListener = this.close.bind(this) as () => void;
private onEscape = (evt:KeyboardEvent) => {
if (evt.keyCode === KeyCodes.ESCAPE) {
this.close();

@ -103,6 +103,12 @@
.wp-table--cell-container .dueDate .icon-pin
display: none
.wp-table--cell-container
&.duration,
&.startDate,
&.dueDate
overflow: visible
.wp-table--cell-container.startDate
padding-left: 24px

Loading…
Cancel
Save