1- import { Show , onMount , onCleanup } from "solid-js" ;
1+ import { Show , onMount , onCleanup , createSignal , children , createEffect } from "solid-js" ;
22import { HiOutlineXMark } from "solid-icons/hi" ;
3+ import { render } from "solid-js/web" ;
4+
5+ const MIN_WIDTH = 320 ;
6+ const MIN_HEIGHT = 200 ;
7+ const SNAP_DISTANCE = 24 ;
8+
9+ // Module-level counter shared across all Dialog instances
10+ let topZIndex = 100 ;
311
412export default function Dialog ( props ) {
13+ let dialog ;
14+
15+ const [ zIndex , setZIndex ] = createSignal ( topZIndex ) ;
16+
17+ const bringToFront = ( ) => {
18+ topZIndex += 1 ;
19+ setZIndex ( topZIndex ) ;
20+ } ;
21+
22+ createEffect ( ( ) => {
23+ if ( ! props . open || ! dialog ) return ;
24+
25+ const header = dialog . querySelector ( ".dialog_header" ) ;
26+ if ( ! header ) return ;
27+
28+ if ( header . querySelector ( ".dialog_close" ) ) return ;
29+
30+ const button = document . createElement ( "c" ) ;
31+ button . className = "dialog_close" ;
32+ button . setAttribute ( "aria-label" , "Close dialog" ) ;
33+
34+ const dispose = render (
35+ ( ) => < HiOutlineXMark size = { 24 } /> ,
36+ button
37+ ) ;
38+
39+ button . onclick = ( ) => props . onClose ?. ( ) ;
40+
41+ header . appendChild ( button ) ;
42+
43+ onCleanup ( ( ) => {
44+ dispose ( ) ;
45+ button . remove ( ) ;
46+ } ) ;
47+ } ) ;
48+
49+ // Bring to front whenever the dialog opens
50+ createEffect ( ( ) => {
51+ if ( props . open ) bringToFront ( ) ;
52+ } ) ;
53+
54+ let dragging = false ;
55+ let resizing = false ;
56+ let resizeDir = "" ;
57+
58+ const [ rect , setRect ] = createSignal ( {
59+ x : window . innerWidth / 2 - 500 ,
60+ y : window . innerHeight / 2 - 400 ,
61+ width : 1000 ,
62+ height : 800 ,
63+ } ) ;
64+
565 const handleKeyDown = ( e ) => {
6- if ( e . key === "Escape" ) {
7- props . onClose ?. ( ) ;
8- }
66+ if ( e . key === "Escape" ) props . onClose ?. ( ) ;
67+ } ;
68+
69+ const beginDrag = ( e ) => {
70+ bringToFront ( ) ;
71+
72+ const header = e . target . closest ( ".dialog_header" ) ;
73+ if ( ! header ) return ;
74+
75+ dragging = true ;
76+
77+ const startX = e . clientX ;
78+ const startY = e . clientY ;
79+ const start = rect ( ) ;
80+
81+ const move = ( ev ) => {
82+ if ( ! dragging ) return ;
83+
84+ setRect ( {
85+ ...start ,
86+ x : start . x + ev . clientX - startX ,
87+ y : start . y + ev . clientY - startY ,
88+ } ) ;
89+ } ;
90+
91+ const up = ( ) => {
92+ dragging = false ;
93+
94+ const r = rect ( ) ;
95+
96+ if ( r . x <= SNAP_DISTANCE ) {
97+ setRect ( {
98+ x : 0 ,
99+ y : 0 ,
100+ width : window . innerWidth / 2 ,
101+ height : window . innerHeight ,
102+ } ) ;
103+ } else if (
104+ r . x + r . width >=
105+ window . innerWidth - SNAP_DISTANCE
106+ ) {
107+ setRect ( {
108+ x : window . innerWidth / 2 ,
109+ y : 0 ,
110+ width : window . innerWidth / 2 ,
111+ height : window . innerHeight ,
112+ } ) ;
113+ } else if ( r . y <= SNAP_DISTANCE ) {
114+ setRect ( {
115+ x : 0 ,
116+ y : 0 ,
117+ width : window . innerWidth ,
118+ height : window . innerHeight / 2 ,
119+ } ) ;
120+ }
121+
122+ window . removeEventListener ( "pointermove" , move ) ;
123+ window . removeEventListener ( "pointerup" , up ) ;
124+ } ;
125+
126+ window . addEventListener ( "pointermove" , move ) ;
127+ window . addEventListener ( "pointerup" , up ) ;
128+ } ;
129+
130+ const beginResize = ( e , dir ) => {
131+ e . stopPropagation ( ) ;
132+ bringToFront ( ) ;
133+
134+ resizing = true ;
135+ resizeDir = dir ;
136+
137+ const start = rect ( ) ;
138+ const sx = e . clientX ;
139+ const sy = e . clientY ;
140+
141+ const move = ( ev ) => {
142+ let { x, y, width, height } = start ;
143+
144+ const dx = ev . clientX - sx ;
145+ const dy = ev . clientY - sy ;
146+
147+ if ( resizeDir . includes ( "e" ) )
148+ width = Math . max ( MIN_WIDTH , start . width + dx ) ;
149+
150+ if ( resizeDir . includes ( "s" ) )
151+ height = Math . max ( MIN_HEIGHT , start . height + dy ) ;
152+
153+ if ( resizeDir . includes ( "w" ) ) {
154+ width = Math . max ( MIN_WIDTH , start . width - dx ) ;
155+ x = start . x + ( start . width - width ) ;
156+ }
157+
158+ if ( resizeDir . includes ( "n" ) ) {
159+ height = Math . max ( MIN_HEIGHT , start . height - dy ) ;
160+ y = start . y + ( start . height - height ) ;
161+ }
162+
163+ setRect ( { x, y, width, height } ) ;
164+ } ;
165+
166+ const up = ( ) => {
167+ resizing = false ;
168+ window . removeEventListener ( "pointermove" , move ) ;
169+ window . removeEventListener ( "pointerup" , up ) ;
170+ } ;
171+
172+ window . addEventListener ( "pointermove" , move ) ;
173+ window . addEventListener ( "pointerup" , up ) ;
9174 } ;
10175
11176 onMount ( ( ) => {
@@ -16,26 +181,39 @@ export default function Dialog(props) {
16181 document . removeEventListener ( "keydown" , handleKeyDown ) ;
17182 } ) ;
18183
184+ const resolvedChildren = children ( ( ) => props . children ) ;
185+
19186 return (
20187 < Show when = { props . open } >
21188 < div
22189 class = "dialog_overlay"
23- onClick = { ( ) => props . onClose ?. ( ) }
190+ style = { { "z-index" : zIndex ( ) } }
24191 >
25192 < div
193+ ref = { dialog }
26194 class = "dialog"
195+ onPointerDown = { ( e ) => {
196+ bringToFront ( ) ;
197+ beginDrag ( e ) ;
198+ } }
27199 onClick = { ( e ) => e . stopPropagation ( ) }
200+ style = { {
201+ left : `${ rect ( ) . x } px` ,
202+ top : `${ rect ( ) . y } px` ,
203+ width : `${ rect ( ) . width } px` ,
204+ height : `${ rect ( ) . height } px` ,
205+ } }
28206 >
29- < button
30- class = "dialog_close"
31- type = "button"
32- aria-label = "Close dialog"
33- onClick = { ( ) => props . onClose ?. ( ) }
34- >
35- < HiOutlineXMark size = { 24 } />
36- </ button >
37-
38207 { props . children }
208+
209+ < div class = "resize n" onPointerDown = { ( e ) => beginResize ( e , "n" ) } />
210+ < div class = "resize s" onPointerDown = { ( e ) => beginResize ( e , "s" ) } />
211+ < div class = "resize e" onPointerDown = { ( e ) => beginResize ( e , "e" ) } />
212+ < div class = "resize w" onPointerDown = { ( e ) => beginResize ( e , "w" ) } />
213+ < div class = "resize ne" onPointerDown = { ( e ) => beginResize ( e , "ne" ) } />
214+ < div class = "resize nw" onPointerDown = { ( e ) => beginResize ( e , "nw" ) } />
215+ < div class = "resize se" onPointerDown = { ( e ) => beginResize ( e , "se" ) } />
216+ < div class = "resize sw" onPointerDown = { ( e ) => beginResize ( e , "sw" ) } />
39217 </ div >
40218 </ div >
41219 </ Show >
0 commit comments