@@ -2,41 +2,48 @@ type DialogPosition = "left" | "right" | "top" | "bottom" | "max";
22type DialogWidth = "small-width" | "medium-width" | "large-width" ;
33
44interface DialogOptions {
5- id ?: string | undefined ;
6- title ?: string | undefined ;
7- position ?: DialogPosition | null | undefined ;
8- width ?: DialogWidth | null | undefined ;
9- onClose ?: ( ( ) => void ) | undefined ;
5+ id ?: string ;
6+ title ?: string ;
7+ position ?: DialogPosition | null ;
8+ width ?: DialogWidth | null ;
9+ onClose ?: ( ) => void ;
1010 autoRemove ?: boolean ;
11- headerContent ?: string | undefined ;
12- bodyContent ?: string | undefined ;
13- footerContent ?: string | undefined ;
11+ headerContent ?: string ;
12+ bodyContent ?: string ;
13+ footerContent ?: string ;
1414 isModal ?: boolean ;
15+ draggable ?: boolean ;
1516}
1617
1718export class DialogComponent {
1819 protected readonly bodyElement : HTMLElement ;
1920 private readonly dialog : HTMLDialogElement ;
20- private options : DialogOptions ;
21+ private options : Required < Omit < DialogOptions , "id" | "onClose" | "headerContent" | "bodyContent" | "footerContent" | "isModal" > > &
22+ Pick < DialogOptions , "id" | "onClose" | "headerContent" | "bodyContent" | "footerContent" | "isModal" > ;
2123 private readonly headerElement : HTMLElement ;
2224 private readonly footerElement : HTMLElement ;
25+ private isDragging = false ;
26+ private dragOffsetX = 0 ;
27+ private dragOffsetY = 0 ;
2328
2429 constructor ( options : DialogOptions = { } ) {
25- this . headerElement = document . createElement ( "header " ) ;
30+ this . headerElement = document . createElement ( "div " ) ;
2631 this . bodyElement = document . createElement ( "div" ) ;
27- this . footerElement = document . createElement ( "footer " ) ;
32+ this . footerElement = document . createElement ( "div " ) ;
2833
2934 this . options = {
30- id : options . id ,
3135 title : options . title ?? "Dialog" ,
3236 position : options . position ?? null ,
3337 width : options . width ?? null ,
34- onClose : options . onClose ,
3538 autoRemove : options . autoRemove ?? true ,
36- headerContent : options . headerContent ,
37- bodyContent : options . bodyContent ,
38- footerContent : options . footerContent ,
39- isModal : options . isModal ?? false
39+ isModal : options . isModal ?? false ,
40+ draggable : options . draggable ?? false ,
41+
42+ ...( options . id !== undefined && { id : options . id } ) ,
43+ ...( options . onClose && { onClose : options . onClose } ) ,
44+ ...( options . headerContent && { headerContent : options . headerContent } ) ,
45+ ...( options . bodyContent && { bodyContent : options . bodyContent } ) ,
46+ ...( options . footerContent && { footerContent : options . footerContent } ) ,
4047 } ;
4148
4249 this . dialog = document . createElement ( "dialog" ) ;
@@ -58,6 +65,32 @@ export class DialogComponent {
5865 document . body . appendChild ( this . dialog ) ;
5966 ui ( this . dialog ) ;
6067 document . addEventListener ( "keydown" , this . handleEscape ) ;
68+ if ( this . options . draggable ) {
69+ setTimeout ( ( ) => {
70+ const el = this . element ;
71+
72+ // Read once
73+ const rect = el . getBoundingClientRect ( ) ;
74+
75+ requestAnimationFrame ( ( ) => {
76+ // Freeze BeerCSS animation immediately
77+ el . style . transition = "none" ;
78+ el . style . animation = "none" ;
79+
80+ // Lock position exactly where BeerCSS put it
81+ el . style . position = "fixed" ;
82+ el . style . left = "0" ;
83+ el . style . top = "0" ;
84+ el . style . transform = `translate3d(${ rect . left } px, ${ rect . top } px, 0)` ;
85+
86+ // Force style flush
87+ el . getBoundingClientRect ( ) ;
88+
89+ el . style . transition = "transform var(--speed1) ease-out" ;
90+ el . style . transform = `translate3d(${ rect . left } px, ${ rect . top } px, 0)` ;
91+ } ) ;
92+ } , 300 ) ;
93+ }
6194 }
6295
6396 public get element ( ) : HTMLDialogElement {
@@ -76,34 +109,34 @@ export class DialogComponent {
76109
77110 if ( this . options . autoRemove ) {
78111 setTimeout ( ( ) => this . dialog . remove ( ) , 200 ) ;
112+ this . removeOwnOverlay ( ) ;
79113 }
80114
81115 this . options . onClose ?.( ) ;
82116 }
83117
84-
85118 private createDialogContent ( ) : void {
86119 const dialogContent = document . createElement ( "div" ) ;
87120
88121 if ( this . options . headerContent ) {
89122 this . headerElement . innerHTML = this . options . headerContent ;
90- this . headerElement . className = "dialog-header" ;
123+ this . headerElement . className = "dialog-header right-align " ;
91124 } else {
92125 this . createDefaultHeader ( ) ;
93126 }
94127 dialogContent . appendChild ( this . headerElement ) ;
95128
96129 if ( this . options . bodyContent ) {
97130 this . bodyElement . innerHTML = this . options . bodyContent ;
98- this . bodyElement . className = "dialog-body" ;
131+ this . bodyElement . className = "dialog-body top-padding " ;
99132 dialogContent . appendChild ( this . bodyElement ) ;
100133 } else {
101134 this . createDefaultBody ( ) ;
102135 }
103136
104137 if ( this . options . footerContent ) {
105138 this . footerElement . innerHTML = this . options . footerContent ;
106- this . footerElement . className = "dialog-footer bottom fixed surface-container-high " ;
139+ this . footerElement . className = "dialog-footer right-align " ;
107140 dialogContent . appendChild ( this . footerElement ) ;
108141 } else {
109142 this . createDefaultFooter ( ) ;
@@ -112,25 +145,35 @@ export class DialogComponent {
112145 this . dialog . appendChild ( dialogContent ) ;
113146 }
114147
148+ private removeOwnOverlay ( ) : void {
149+ const prev = this . dialog . previousElementSibling ;
150+ if ( prev instanceof HTMLElement && prev . classList . contains ( "overlay" ) ) {
151+ prev . remove ( ) ;
152+ }
153+ }
154+
115155 private createDefaultHeader ( ) : void {
116156 const nav = document . createElement ( "nav" ) ;
117- nav . className = "row" ;
157+ nav . className = "row tiny-space dialog-header" ;
158+
159+ if ( this . options . draggable ) {
160+ const handle = document . createElement ( "i" ) ;
161+ handle . className = "handle" ;
162+ handle . innerHTML = "drag_indicator" ;
163+ handle . addEventListener ( "mousedown" , this . startDrag ) ;
164+ nav . appendChild ( handle ) ;
165+ }
118166
119167 const header = document . createElement ( "h5" ) ;
120168 header . className = "max" ;
121- if ( this . options . title ) {
122- header . innerText = this . options . title ;
123- }
169+ header . innerText = this . options . title ;
124170
125171 const closeButton = document . createElement ( "button" ) ;
126172 closeButton . className = "circle transparent" ;
127- closeButton . id = "close-button" ;
128173 closeButton . innerHTML = "<i>close</i>" ;
129174 closeButton . addEventListener ( "click" , ( ) => this . close ( ) ) ;
130175
131- nav . appendChild ( header ) ;
132- nav . appendChild ( closeButton ) ;
133-
176+ nav . append ( header , closeButton ) ;
134177 this . headerElement . appendChild ( nav ) ;
135178 }
136179
@@ -155,12 +198,49 @@ export class DialogComponent {
155198 }
156199 } ;
157200
201+ private startDrag = ( e : MouseEvent ) => {
202+ if ( ! this . options . draggable || e . button !== 0 ) return ;
203+
204+ e . preventDefault ( ) ;
205+ e . stopPropagation ( ) ;
206+
207+ this . isDragging = true ;
208+ this . element . classList . add ( "dragging" ) ;
209+
210+ const rect = this . element . getBoundingClientRect ( ) ;
211+ this . dragOffsetX = e . clientX - rect . left ;
212+ this . dragOffsetY = e . clientY - rect . top ;
213+
214+ document . addEventListener ( "mousemove" , this . onDrag ) ;
215+ document . addEventListener ( "mouseup" , this . stopDrag ) ;
216+ } ;
217+
218+
219+ private onDrag = ( e : MouseEvent ) => {
220+ if ( ! this . isDragging || e . buttons !== 1 ) return ;
221+
222+ const x = e . clientX - this . dragOffsetX ;
223+ const y = e . clientY - this . dragOffsetY ;
224+
225+ this . element . style . transform = `translate3d(${ x } px, ${ y } px, 0)` ;
226+ } ;
227+
228+
229+ private stopDrag = ( ) => {
230+ this . isDragging = false ;
231+ this . element . classList . remove ( "dragging" ) ;
232+ document . body . classList . remove ( "no-select" ) ;
233+
234+ document . removeEventListener ( "mousemove" , this . onDrag ) ;
235+ document . removeEventListener ( "mouseup" , this . stopDrag ) ;
236+ } ;
237+
238+
158239 public handleResize = ( ) => {
159240 if ( window . innerWidth < 600 ) {
160241 this . dialog . classList . add ( "max" ) ;
161242 } else {
162243 this . dialog . classList . remove ( "max" ) ;
163244 }
164245 } ;
165-
166246}
0 commit comments