Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 apps/showcase/public/llms/components/dialog.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ Dialog is a container to display content in an overlay window.
| maximizable | boolean | false | Whether the dialog can be displayed full screen. |
| keepInViewport | boolean | true | Keeps dialog in the viewport. |
| focusTrap | boolean | true | When enabled, can only focus on elements inside the dialog. |
| selectableTitle | boolean | false | When enabled, the title text is selectable and dragging only works on other parts of the header. |
| transitionOptions | string | 150ms cubic-bezier(0, 0, 0.2, 1) | Transition options of the animation. **(Deprecated)** |
| maskMotionOptions | InputSignal<MotionOptions> | ... | The motion options for the mask. |
| motionOptions | InputSignal<MotionOptions> | ... | The motion options. |
Expand Down
29 changes: 29 additions & 0 deletions packages/primeng/src/dialog/dialog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ describe('Dialog', () => {
expect(dialogInstance.keepInViewport).toBe(true);
expect(dialogInstance.rtl).toBe(false);
expect(dialogInstance.role).toBe('dialog');
expect(dialogInstance.selectableTitle).toBe(false);
});

it('should accept custom input values', async () => {
Expand Down Expand Up @@ -1275,6 +1276,34 @@ describe('Dialog', () => {
}
});

it('should not initiate drag when mousedown is on title with selectableTitle enabled', async () => {
component.visible = true;
fixture.changeDetectorRef.markForCheck();
await fixture.whenStable();
await new Promise((resolve) => setTimeout(resolve, 0));

dialogInstance.selectableTitle = true;
spyOn(dialogInstance, 'initDrag');

const titleEl = fixture.debugElement.query(By.css('.p-dialog-title'));
titleEl?.nativeElement.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
expect(dialogInstance.initDrag).not.toHaveBeenCalled();
});

it('should still initiate drag from non-title header area with selectableTitle enabled', async () => {
component.visible = true;
fixture.changeDetectorRef.markForCheck();
await fixture.whenStable();
await new Promise((resolve) => setTimeout(resolve, 0));

dialogInstance.selectableTitle = true;
spyOn(dialogInstance, 'initDrag');

const header = fixture.debugElement.query(By.css('.p-dialog-header'));
header?.nativeElement.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
expect(dialogInstance.initDrag).toHaveBeenCalled();
});

it('should handle resize initialization', async () => {
component.resizable = true;
fixture.changeDetectorRef.markForCheck();
Expand Down
16 changes: 15 additions & 1 deletion packages/primeng/src/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const DIALOG_INSTANCE = new InjectionToken<Dialog>('DIALOG_INSTANCE');
<ng-template #notHeadless>
<div *ngIf="resizable" [class]="cx('resizeHandle')" [pBind]="ptm('resizeHandle')" [style.z-index]="90" (mousedown)="initResize($event)"></div>
<div #titlebar [class]="cx('header')" [pBind]="ptm('header')" (mousedown)="initDrag($event)" *ngIf="showHeader">
<span [id]="ariaLabelledBy" [class]="cx('title')" [pBind]="ptm('title')" *ngIf="!_headerTemplate && !headerTemplate && !headerT">{{ header }}</span>
<span [id]="ariaLabelledBy" [class]="cx('title')" [pBind]="ptm('title')" *ngIf="!_headerTemplate && !headerTemplate && !headerT" (mousedown)="selectableTitle ? $event.stopPropagation() : null">{{ header }}</span>
<ng-container *ngTemplateOutlet="_headerTemplate || headerTemplate || headerT; context: { ariaLabelledBy: ariaLabelledBy }"></ng-container>
<div [class]="cx('headerActions')" [pBind]="ptm('headerActions')">
<p-button
Expand Down Expand Up @@ -301,6 +301,11 @@ export class Dialog extends BaseComponent<DialogPassThrough> implements OnInit,
* @group Props
*/
@Input({ transform: booleanAttribute }) focusTrap: boolean = true;
/**
* When enabled, the title text is selectable and dragging only works on other parts of the header.
* @group Props
*/
@Input({ transform: booleanAttribute }) selectableTitle: boolean = false;
/**
* Transition options of the animation.
* @deprecated since v21.0.0. Use `motionOptions` instead.
Expand Down Expand Up @@ -844,6 +849,15 @@ export class Dialog extends BaseComponent<DialogPassThrough> implements OnInit,
}

if (this.draggable) {
if (this.selectableTitle) {
// Clear any selected title text when the user starts dragging from a non-title area.
// anchorNode is where the selection started and contains() ensures we only clear
// selections inside this dialog, leaving any selections outside untouched.
const selection = this.document.defaultView?.getSelection();
if (selection?.rangeCount && this.container()?.contains(selection.anchorNode)) {
selection.removeAllRanges();
}
}
this.dragging = true;
this.lastPageX = event.pageX;
this.lastPageY = event.pageY;
Expand Down
5 changes: 5 additions & 0 deletions packages/primeng/src/dynamicdialog/dynamicdialog-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ export class DynamicDialogConfig<DataType = any, InputValuesType extends Record<
* @group Props
*/
draggable?: boolean = false;
/**
* When enabled, the title text is selectable and dragging only works on other parts of the header.
* @group Props
*/
selectableTitle?: boolean = false;
/**
* Keeps dialog in the viewport.
* @group Props
Expand Down
1 change: 1 addition & 0 deletions packages/primeng/src/dynamicdialog/dynamicdialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const DYNAMIC_DIALOG_INSTANCE = new InjectionToken<DynamicDialog>('DYNAMIC_DIALO
[maximizable]="maximizable"
[keepInViewport]="keepInViewport"
[focusTrap]="ddconfig?.focusTrap !== false"
[selectableTitle]="ddconfig?.selectableTitle"
[transitionOptions]="ddconfig?.transitionOptions || '150ms cubic-bezier(0, 0, 0.2, 1)'"
[closeAriaLabel]="ddconfig?.closeAriaLabel || defaultCloseAriaLabel"
[minimizeIcon]="minimizeIcon"
Expand Down
Loading