@bit.rhplus/draggable-modal 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,344 @@
1
+ # Návod: Resize modálního okna bez knihovny
2
+
3
+ Kompletní průvodce implementací resize (změna velikosti myší) na Ant Design 5 modálním okně v Reactu — bez použití jakékoliv resize knihovny.
4
+
5
+ ---
6
+
7
+ ## Kontext
8
+
9
+ Komponenta `DraggableModal` rozšiřuje Ant Design `<Modal>` o drag (přetahování) pomocí `react-draggable`. Cílem bylo přidat i resize — změnu velikosti táhnutím za hrany/rohy.
10
+
11
+ **Důležité:** `react-draggable` je výhradně pro drag. Resize nenabízí. Musíme ho implementovat ručně.
12
+
13
+ ---
14
+
15
+ ## Jak resize funguje — princip
16
+
17
+ Resize funguje na třech fázích mouse eventů:
18
+
19
+ ```
20
+ mousedown na handle → zapamatuj startovní pozici a rozměry
21
+
22
+ mousemove na document → počítej delta, aktualizuj stav
23
+
24
+ mouseup na document → ukonči resize, odregistruj listenery
25
+ ```
26
+
27
+ Klíč je přidávat `mousemove`/`mouseup` na **`document`**, ne na handle. Jinak by myš opuštění handle resize přerušila.
28
+
29
+ ---
30
+
31
+ ## Krok 1: Stav pro rozměry
32
+
33
+ ```js
34
+ const parseWidth = (w) => {
35
+ if (typeof w === 'number') return w;
36
+ if (typeof w === 'string') { const n = parseInt(w, 10); return isNaN(n) ? 520 : n; }
37
+ return 520;
38
+ };
39
+
40
+ const [modalSize, setModalSize] = React.useState({
41
+ width: parseWidth(props.width), // vždy číslo (ne "1400px")
42
+ height: null, // null = auto výška z obsahu
43
+ });
44
+ ```
45
+
46
+ **Proč `parseWidth`?**
47
+ Ant Design přijímá `width` jako číslo i string (`"1400px"`). My potřebujeme číslo pro matematiku (`startW + dx`). Parsujeme hned při inicializaci.
48
+
49
+ **Proč `height: null`?**
50
+ Výchozí výška modalu je `auto` — určená obsahem. Výšku začínáme sledovat až při prvním resize (pak ji fixujeme na pixely).
51
+
52
+ ---
53
+
54
+ ## Krok 2: Resize handles — průhledné aktivní zóny
55
+
56
+ Handles jsou prázdné `<div>` elementy bez viditelného obsahu. Stačí jim nastavit správný `cursor` a dostatečnou plochu pro klik.
57
+
58
+ ```css
59
+ .drm-handle {
60
+ position: absolute;
61
+ z-index: 10;
62
+ user-select: none;
63
+ pointer-events: auto; /* KRITICKÉ — viz sekce Nástrahy */
64
+ }
65
+
66
+ /* Pravá hrana */
67
+ .drm-handle-e { top: 12px; right: -4px; width: 8px; height: calc(100% - 24px); cursor: e-resize; }
68
+
69
+ /* Levá hrana */
70
+ .drm-handle-w { top: 12px; left: -4px; width: 8px; height: calc(100% - 24px); cursor: w-resize; }
71
+
72
+ /* Spodní hrana */
73
+ .drm-handle-s { bottom: -4px; left: 12px; width: calc(100% - 24px); height: 8px; cursor: s-resize; }
74
+
75
+ /* Pravý dolní roh */
76
+ .drm-handle-se { bottom: -4px; right: -4px; width: 16px; height: 16px; cursor: se-resize; }
77
+
78
+ /* Levý dolní roh */
79
+ .drm-handle-sw { bottom: -4px; left: -4px; width: 16px; height: 16px; cursor: sw-resize; }
80
+ ```
81
+
82
+ Handles jsou umístěny na `-4px` vně okna (polovina šířky 8px přesahuje). To vytváří přirozený aktivní prostor na samé hraně.
83
+
84
+ ---
85
+
86
+ ## Krok 3: Logika resize
87
+
88
+ ```js
89
+ const handleResizeStart = (e, direction) => {
90
+ e.preventDefault();
91
+ e.stopPropagation(); // zabrání spuštění drag při tahu za handle
92
+
93
+ // Změříme aktuální rozměry z DOM (ne ze stavu — stav může být zaostalý)
94
+ const modalEl = draggleRef.current?.querySelector('.ant-modal');
95
+ const contentEl = draggleRef.current?.querySelector('.ant-modal-content');
96
+ const startX = e.clientX;
97
+ const startY = e.clientY;
98
+ const startW = modalEl?.offsetWidth || modalSize.width;
99
+ const startH = contentEl?.offsetHeight || modalSize.height || 400;
100
+
101
+ const onMouseMove = (moveEvent) => {
102
+ const dx = moveEvent.clientX - startX; // kolik px jsme se posunuli
103
+ const dy = moveEvent.clientY - startY;
104
+
105
+ setModalSize(prev => {
106
+ let newW = prev.width;
107
+ let newH = prev.height !== null ? prev.height : startH;
108
+
109
+ if (direction.includes('e')) newW = Math.max(300, startW + dx); // tah doprava
110
+ if (direction.includes('w')) newW = Math.max(300, startW - dx); // tah doleva
111
+ if (direction.includes('s')) newH = Math.max(200, startH + dy); // tah dolů
112
+
113
+ return { width: newW, height: newH };
114
+ });
115
+ };
116
+
117
+ const onMouseUp = () => {
118
+ document.removeEventListener('mousemove', onMouseMove);
119
+ document.removeEventListener('mouseup', onMouseUp);
120
+ };
121
+
122
+ // DŮLEŽITÉ: listenery na document, ne na handle
123
+ document.addEventListener('mousemove', onMouseMove);
124
+ document.addEventListener('mouseup', onMouseUp);
125
+ };
126
+ ```
127
+
128
+ **Proč čteme z DOM a ne ze stavu?**
129
+ Při zahájení resize chceme **aktuální vizuální rozměry**. Stav `modalSize` může být zaostalý o render cyklus. `offsetWidth`/`offsetHeight` vrací vždy aktuální hodnotu z DOM.
130
+
131
+ **Minimální rozměry:**
132
+ `Math.max(300, ...)` a `Math.max(200, ...)` zabrání zborcení layoutu.
133
+
134
+ ---
135
+
136
+ ## Krok 4: Aplikace rozměrů na modal
137
+
138
+ ```jsx
139
+ <Modal
140
+ {...props}
141
+ width={resizable ? modalSize.width : props.width}
142
+ styles={resolveStyles()}
143
+ >
144
+ ```
145
+
146
+ ```js
147
+ const resolveStyles = () => {
148
+ if (resizable && modalSize.height !== null) {
149
+ return {
150
+ body: {
151
+ paddingBottom: '30px',
152
+ height: `calc(${modalSize.height}px - 110px)`, // odečteme header + footer
153
+ overflow: 'auto',
154
+ display: 'flex',
155
+ flexDirection: 'column',
156
+ },
157
+ content: {
158
+ height: `${modalSize.height}px`,
159
+ display: 'flex',
160
+ flexDirection: 'column',
161
+ },
162
+ };
163
+ }
164
+ // ... fallback pro statický height prop
165
+ };
166
+ ```
167
+
168
+ `110px` je odhadovaná výška headeru + footeru. `overflow: auto` na body zajistí scrollování při přetečení obsahu.
169
+
170
+ ---
171
+
172
+ ## Krok 5: Kam umístit handles — nástraha Ant Design
173
+
174
+ Naivní přístup: vložit handles přímo do `modalRender` wrapperu:
175
+
176
+ ```jsx
177
+ // ❌ NEFUNGUJE
178
+ modalRender={(modal) => (
179
+ <Draggable>
180
+ <div ref={draggleRef}>
181
+ {modal}
182
+ {resizeHandles} {/* <-- tady NE */}
183
+ </div>
184
+ </Draggable>
185
+ )}
186
+ ```
187
+
188
+ **Proč ne?** Wrapper div je block-level element — má šířku `100% viewportu`. Handles s `position: absolute; right: -4px` se zobrazí na okraji viewportu, ne modálního okna. Uživatel je nevidí.
189
+
190
+ ```
191
+ .ant-modal-wrap (100vw)
192
+ [draggleRef div = 100vw] ← handles jsou tady → špatně
193
+ .ant-modal (1400px, margin:auto)
194
+ .ant-modal-content
195
+ ```
196
+
197
+ ### Správné řešení: ReactDOM.createPortal do `.ant-modal-content`
198
+
199
+ ```jsx
200
+ // ✅ SPRÁVNĚ
201
+ {resizable && antModalEl && ReactDOM.createPortal(resizeHandles, antModalEl)}
202
+ // antModalEl = element .ant-modal-content
203
+ ```
204
+
205
+ `.ant-modal-content` má `position: relative` → handles s `position: absolute` jsou relativní k němu. Handles jsou přesně na hranách viditelného okna.
206
+
207
+ ```
208
+ .ant-modal-content (position: relative) ← portal target
209
+ .ant-modal-header
210
+ .ant-modal-body
211
+ .ant-modal-footer
212
+ [drm-handle-e] ← position: absolute, right: -4px ✓
213
+ [drm-handle-se] ← position: absolute, bottom: -4px, right: -4px ✓
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Krok 6: Detekce `.ant-modal-content` elementu
219
+
220
+ Portal cíl musíme najít v DOM poté, co Ant Design modal narenduje. Použijeme `useLayoutEffect` — běží synchronně po DOM mutaci:
221
+
222
+ ```js
223
+ const [antModalEl, setAntModalEl] = React.useState(null);
224
+
225
+ React.useLayoutEffect(() => {
226
+ if (resizable && open && draggleRef.current) {
227
+ setAntModalEl(draggleRef.current.querySelector('.ant-modal-content'));
228
+ } else {
229
+ setAntModalEl(null);
230
+ }
231
+ }, [resizable, open]);
232
+ ```
233
+
234
+ **Proč `useLayoutEffect` a ne `useEffect`?**
235
+ `useEffect` běží asynchronně po paint — uživatel by na první render viděl modal bez handles. `useLayoutEffect` běží synchronně před paint, takže handles se objeví ihned s modalem (druhý render, ale bez vizuálního bliknutí).
236
+
237
+ **Tok na první otevření:**
238
+ 1. `open` změní na `true` → první render → `antModalEl` je `null` → handles se nevykreslí
239
+ 2. `useLayoutEffect` najde `.ant-modal-content`, zavolá `setAntModalEl`
240
+ 3. Druhý render → handles se vykreslí přes portal
241
+ 4. Vše synchronní — uživatel nevidí blikání
242
+
243
+ ---
244
+
245
+ ## Nástraha: `pointer-events: none` na `.ant-modal`
246
+
247
+ Ant Design 5 nastavuje na `.ant-modal`:
248
+
249
+ ```js
250
+ // node_modules/antd/es/modal/style/index.js
251
+ '.ant-modal': {
252
+ pointerEvents: 'none', // ← zabraňuje klikům mimo .ant-modal-content
253
+ }
254
+ '.ant-modal-content': {
255
+ pointerEvents: 'auto', // ← re-aktivuje pro obsah
256
+ }
257
+ ```
258
+
259
+ Pokud bychom portálovali handles do `.ant-modal` (ne do `.ant-modal-content`), handles by dědily `pointer-events: none` → `onMouseDown` by se nikdy nespustil.
260
+
261
+ Proto portálujeme do `.ant-modal-content` (má `pointer-events: auto`) + přidáme `pointer-events: auto` na `.drm-handle` jako pojistku.
262
+
263
+ ---
264
+
265
+ ## Nástraha: `e.stopPropagation()` na resize handle
266
+
267
+ Draggable wrapper obaluje celý modal. Bez `stopPropagation` by `mousedown` na resize handle také spustil drag. Výsledek: modal se táhne místo resize.
268
+
269
+ ```js
270
+ const handleResizeStart = (e, direction) => {
271
+ e.preventDefault();
272
+ e.stopPropagation(); // zabrání bubblingu k Draggable
273
+ // ...
274
+ };
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Výsledný strom v DOM
280
+
281
+ ```
282
+ [draggleRef div] ← Draggable wrapper (full-width)
283
+ .ant-modal ← width prop → kontroluje vizuální šířku
284
+ .ant-modal-content ← portal target (position: relative)
285
+ .ant-modal-header
286
+ .ant-modal-body
287
+ .ant-modal-footer
288
+ .drm-handle-e ← right: -4px, výška okna
289
+ .drm-handle-w ← left: -4px, výška okna
290
+ .drm-handle-s ← bottom: -4px, šířka okna
291
+ .drm-handle-se ← bottom: -4px, right: -4px
292
+ .drm-handle-sw ← bottom: -4px, left: -4px
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Kompletní flow resize
298
+
299
+ ```
300
+ Uživatel → mousedown na .drm-handle-se
301
+
302
+ handleResizeStart('se')
303
+ - e.stopPropagation() → Draggable nezačne drag
304
+ - uloží: startX, startY, startW (.ant-modal.offsetWidth), startH (.ant-modal-content.offsetHeight)
305
+ - registruje: document.onmousemove, document.onmouseup
306
+
307
+ Uživatel táhne myší
308
+
309
+ onMouseMove
310
+ - dx = clientX - startX, dy = clientY - startY
311
+ - 'se' → newW = max(300, startW + dx), newH = max(200, startH + dy)
312
+ - setModalSize({ width: newW, height: newH })
313
+
314
+ React re-render
315
+ - <Modal width={newW}> → .ant-modal dostane novou šířku
316
+ - styles.content.height = newH → .ant-modal-content dostane novou výšku
317
+ - handles v portalu se přesunou s .ant-modal-content
318
+
319
+ Uživatel pustí myš
320
+
321
+ onMouseUp → removeEventListener (cleanup)
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Použití
327
+
328
+ ```jsx
329
+ // Resize zapnut (default)
330
+ <DraggableModal open={open} onCancel={onClose} title="Okno" width={800}>
331
+ obsah
332
+ </DraggableModal>
333
+
334
+ // Resize vypnut
335
+ <DraggableModal resizable={false} open={open} onCancel={onClose} title="Okno">
336
+ obsah
337
+ </DraggableModal>
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Proč ne knihovna (re-resizable, react-resizable)?
343
+
344
+ Obě fungují na principu wrapperu kolem elementu. Pro Ant Design Modal je problém, že wrapper musí obalit `.ant-modal`, ale `.ant-modal` je renderován interně knihovnou — nemáme nad ním přímou kontrolu z JSX. Ruční implementace pomocí mouse eventů je přímočařejší a nevyžaduje nové závislosti.
@@ -0,0 +1,344 @@
1
+ # Návod: Resize modálního okna bez knihovny
2
+
3
+ Kompletní průvodce implementací resize (změna velikosti myší) na Ant Design 5 modálním okně v Reactu — bez použití jakékoliv resize knihovny.
4
+
5
+ ---
6
+
7
+ ## Kontext
8
+
9
+ Komponenta `DraggableModal` rozšiřuje Ant Design `<Modal>` o drag (přetahování) pomocí `react-draggable`. Cílem bylo přidat i resize — změnu velikosti táhnutím za hrany/rohy.
10
+
11
+ **Důležité:** `react-draggable` je výhradně pro drag. Resize nenabízí. Musíme ho implementovat ručně.
12
+
13
+ ---
14
+
15
+ ## Jak resize funguje — princip
16
+
17
+ Resize funguje na třech fázích mouse eventů:
18
+
19
+ ```
20
+ mousedown na handle → zapamatuj startovní pozici a rozměry
21
+
22
+ mousemove na document → počítej delta, aktualizuj stav
23
+
24
+ mouseup na document → ukonči resize, odregistruj listenery
25
+ ```
26
+
27
+ Klíč je přidávat `mousemove`/`mouseup` na **`document`**, ne na handle. Jinak by myš opuštění handle resize přerušila.
28
+
29
+ ---
30
+
31
+ ## Krok 1: Stav pro rozměry
32
+
33
+ ```js
34
+ const parseWidth = (w) => {
35
+ if (typeof w === 'number') return w;
36
+ if (typeof w === 'string') { const n = parseInt(w, 10); return isNaN(n) ? 520 : n; }
37
+ return 520;
38
+ };
39
+
40
+ const [modalSize, setModalSize] = React.useState({
41
+ width: parseWidth(props.width), // vždy číslo (ne "1400px")
42
+ height: null, // null = auto výška z obsahu
43
+ });
44
+ ```
45
+
46
+ **Proč `parseWidth`?**
47
+ Ant Design přijímá `width` jako číslo i string (`"1400px"`). My potřebujeme číslo pro matematiku (`startW + dx`). Parsujeme hned při inicializaci.
48
+
49
+ **Proč `height: null`?**
50
+ Výchozí výška modalu je `auto` — určená obsahem. Výšku začínáme sledovat až při prvním resize (pak ji fixujeme na pixely).
51
+
52
+ ---
53
+
54
+ ## Krok 2: Resize handles — průhledné aktivní zóny
55
+
56
+ Handles jsou prázdné `<div>` elementy bez viditelného obsahu. Stačí jim nastavit správný `cursor` a dostatečnou plochu pro klik.
57
+
58
+ ```css
59
+ .drm-handle {
60
+ position: absolute;
61
+ z-index: 10;
62
+ user-select: none;
63
+ pointer-events: auto; /* KRITICKÉ — viz sekce Nástrahy */
64
+ }
65
+
66
+ /* Pravá hrana */
67
+ .drm-handle-e { top: 12px; right: -4px; width: 8px; height: calc(100% - 24px); cursor: e-resize; }
68
+
69
+ /* Levá hrana */
70
+ .drm-handle-w { top: 12px; left: -4px; width: 8px; height: calc(100% - 24px); cursor: w-resize; }
71
+
72
+ /* Spodní hrana */
73
+ .drm-handle-s { bottom: -4px; left: 12px; width: calc(100% - 24px); height: 8px; cursor: s-resize; }
74
+
75
+ /* Pravý dolní roh */
76
+ .drm-handle-se { bottom: -4px; right: -4px; width: 16px; height: 16px; cursor: se-resize; }
77
+
78
+ /* Levý dolní roh */
79
+ .drm-handle-sw { bottom: -4px; left: -4px; width: 16px; height: 16px; cursor: sw-resize; }
80
+ ```
81
+
82
+ Handles jsou umístěny na `-4px` vně okna (polovina šířky 8px přesahuje). To vytváří přirozený aktivní prostor na samé hraně.
83
+
84
+ ---
85
+
86
+ ## Krok 3: Logika resize
87
+
88
+ ```js
89
+ const handleResizeStart = (e, direction) => {
90
+ e.preventDefault();
91
+ e.stopPropagation(); // zabrání spuštění drag při tahu za handle
92
+
93
+ // Změříme aktuální rozměry z DOM (ne ze stavu — stav může být zaostalý)
94
+ const modalEl = draggleRef.current?.querySelector('.ant-modal');
95
+ const contentEl = draggleRef.current?.querySelector('.ant-modal-content');
96
+ const startX = e.clientX;
97
+ const startY = e.clientY;
98
+ const startW = modalEl?.offsetWidth || modalSize.width;
99
+ const startH = contentEl?.offsetHeight || modalSize.height || 400;
100
+
101
+ const onMouseMove = (moveEvent) => {
102
+ const dx = moveEvent.clientX - startX; // kolik px jsme se posunuli
103
+ const dy = moveEvent.clientY - startY;
104
+
105
+ setModalSize(prev => {
106
+ let newW = prev.width;
107
+ let newH = prev.height !== null ? prev.height : startH;
108
+
109
+ if (direction.includes('e')) newW = Math.max(300, startW + dx); // tah doprava
110
+ if (direction.includes('w')) newW = Math.max(300, startW - dx); // tah doleva
111
+ if (direction.includes('s')) newH = Math.max(200, startH + dy); // tah dolů
112
+
113
+ return { width: newW, height: newH };
114
+ });
115
+ };
116
+
117
+ const onMouseUp = () => {
118
+ document.removeEventListener('mousemove', onMouseMove);
119
+ document.removeEventListener('mouseup', onMouseUp);
120
+ };
121
+
122
+ // DŮLEŽITÉ: listenery na document, ne na handle
123
+ document.addEventListener('mousemove', onMouseMove);
124
+ document.addEventListener('mouseup', onMouseUp);
125
+ };
126
+ ```
127
+
128
+ **Proč čteme z DOM a ne ze stavu?**
129
+ Při zahájení resize chceme **aktuální vizuální rozměry**. Stav `modalSize` může být zaostalý o render cyklus. `offsetWidth`/`offsetHeight` vrací vždy aktuální hodnotu z DOM.
130
+
131
+ **Minimální rozměry:**
132
+ `Math.max(300, ...)` a `Math.max(200, ...)` zabrání zborcení layoutu.
133
+
134
+ ---
135
+
136
+ ## Krok 4: Aplikace rozměrů na modal
137
+
138
+ ```jsx
139
+ <Modal
140
+ {...props}
141
+ width={resizable ? modalSize.width : props.width}
142
+ styles={resolveStyles()}
143
+ >
144
+ ```
145
+
146
+ ```js
147
+ const resolveStyles = () => {
148
+ if (resizable && modalSize.height !== null) {
149
+ return {
150
+ body: {
151
+ paddingBottom: '30px',
152
+ height: `calc(${modalSize.height}px - 110px)`, // odečteme header + footer
153
+ overflow: 'auto',
154
+ display: 'flex',
155
+ flexDirection: 'column',
156
+ },
157
+ content: {
158
+ height: `${modalSize.height}px`,
159
+ display: 'flex',
160
+ flexDirection: 'column',
161
+ },
162
+ };
163
+ }
164
+ // ... fallback pro statický height prop
165
+ };
166
+ ```
167
+
168
+ `110px` je odhadovaná výška headeru + footeru. `overflow: auto` na body zajistí scrollování při přetečení obsahu.
169
+
170
+ ---
171
+
172
+ ## Krok 5: Kam umístit handles — nástraha Ant Design
173
+
174
+ Naivní přístup: vložit handles přímo do `modalRender` wrapperu:
175
+
176
+ ```jsx
177
+ // ❌ NEFUNGUJE
178
+ modalRender={(modal) => (
179
+ <Draggable>
180
+ <div ref={draggleRef}>
181
+ {modal}
182
+ {resizeHandles} {/* <-- tady NE */}
183
+ </div>
184
+ </Draggable>
185
+ )}
186
+ ```
187
+
188
+ **Proč ne?** Wrapper div je block-level element — má šířku `100% viewportu`. Handles s `position: absolute; right: -4px` se zobrazí na okraji viewportu, ne modálního okna. Uživatel je nevidí.
189
+
190
+ ```
191
+ .ant-modal-wrap (100vw)
192
+ [draggleRef div = 100vw] ← handles jsou tady → špatně
193
+ .ant-modal (1400px, margin:auto)
194
+ .ant-modal-content
195
+ ```
196
+
197
+ ### Správné řešení: ReactDOM.createPortal do `.ant-modal-content`
198
+
199
+ ```jsx
200
+ // ✅ SPRÁVNĚ
201
+ {resizable && antModalEl && ReactDOM.createPortal(resizeHandles, antModalEl)}
202
+ // antModalEl = element .ant-modal-content
203
+ ```
204
+
205
+ `.ant-modal-content` má `position: relative` → handles s `position: absolute` jsou relativní k němu. Handles jsou přesně na hranách viditelného okna.
206
+
207
+ ```
208
+ .ant-modal-content (position: relative) ← portal target
209
+ .ant-modal-header
210
+ .ant-modal-body
211
+ .ant-modal-footer
212
+ [drm-handle-e] ← position: absolute, right: -4px ✓
213
+ [drm-handle-se] ← position: absolute, bottom: -4px, right: -4px ✓
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Krok 6: Detekce `.ant-modal-content` elementu
219
+
220
+ Portal cíl musíme najít v DOM poté, co Ant Design modal narenduje. Použijeme `useLayoutEffect` — běží synchronně po DOM mutaci:
221
+
222
+ ```js
223
+ const [antModalEl, setAntModalEl] = React.useState(null);
224
+
225
+ React.useLayoutEffect(() => {
226
+ if (resizable && open && draggleRef.current) {
227
+ setAntModalEl(draggleRef.current.querySelector('.ant-modal-content'));
228
+ } else {
229
+ setAntModalEl(null);
230
+ }
231
+ }, [resizable, open]);
232
+ ```
233
+
234
+ **Proč `useLayoutEffect` a ne `useEffect`?**
235
+ `useEffect` běží asynchronně po paint — uživatel by na první render viděl modal bez handles. `useLayoutEffect` běží synchronně před paint, takže handles se objeví ihned s modalem (druhý render, ale bez vizuálního bliknutí).
236
+
237
+ **Tok na první otevření:**
238
+ 1. `open` změní na `true` → první render → `antModalEl` je `null` → handles se nevykreslí
239
+ 2. `useLayoutEffect` najde `.ant-modal-content`, zavolá `setAntModalEl`
240
+ 3. Druhý render → handles se vykreslí přes portal
241
+ 4. Vše synchronní — uživatel nevidí blikání
242
+
243
+ ---
244
+
245
+ ## Nástraha: `pointer-events: none` na `.ant-modal`
246
+
247
+ Ant Design 5 nastavuje na `.ant-modal`:
248
+
249
+ ```js
250
+ // node_modules/antd/es/modal/style/index.js
251
+ '.ant-modal': {
252
+ pointerEvents: 'none', // ← zabraňuje klikům mimo .ant-modal-content
253
+ }
254
+ '.ant-modal-content': {
255
+ pointerEvents: 'auto', // ← re-aktivuje pro obsah
256
+ }
257
+ ```
258
+
259
+ Pokud bychom portálovali handles do `.ant-modal` (ne do `.ant-modal-content`), handles by dědily `pointer-events: none` → `onMouseDown` by se nikdy nespustil.
260
+
261
+ Proto portálujeme do `.ant-modal-content` (má `pointer-events: auto`) + přidáme `pointer-events: auto` na `.drm-handle` jako pojistku.
262
+
263
+ ---
264
+
265
+ ## Nástraha: `e.stopPropagation()` na resize handle
266
+
267
+ Draggable wrapper obaluje celý modal. Bez `stopPropagation` by `mousedown` na resize handle také spustil drag. Výsledek: modal se táhne místo resize.
268
+
269
+ ```js
270
+ const handleResizeStart = (e, direction) => {
271
+ e.preventDefault();
272
+ e.stopPropagation(); // zabrání bubblingu k Draggable
273
+ // ...
274
+ };
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Výsledný strom v DOM
280
+
281
+ ```
282
+ [draggleRef div] ← Draggable wrapper (full-width)
283
+ .ant-modal ← width prop → kontroluje vizuální šířku
284
+ .ant-modal-content ← portal target (position: relative)
285
+ .ant-modal-header
286
+ .ant-modal-body
287
+ .ant-modal-footer
288
+ .drm-handle-e ← right: -4px, výška okna
289
+ .drm-handle-w ← left: -4px, výška okna
290
+ .drm-handle-s ← bottom: -4px, šířka okna
291
+ .drm-handle-se ← bottom: -4px, right: -4px
292
+ .drm-handle-sw ← bottom: -4px, left: -4px
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Kompletní flow resize
298
+
299
+ ```
300
+ Uživatel → mousedown na .drm-handle-se
301
+
302
+ handleResizeStart('se')
303
+ - e.stopPropagation() → Draggable nezačne drag
304
+ - uloží: startX, startY, startW (.ant-modal.offsetWidth), startH (.ant-modal-content.offsetHeight)
305
+ - registruje: document.onmousemove, document.onmouseup
306
+
307
+ Uživatel táhne myší
308
+
309
+ onMouseMove
310
+ - dx = clientX - startX, dy = clientY - startY
311
+ - 'se' → newW = max(300, startW + dx), newH = max(200, startH + dy)
312
+ - setModalSize({ width: newW, height: newH })
313
+
314
+ React re-render
315
+ - <Modal width={newW}> → .ant-modal dostane novou šířku
316
+ - styles.content.height = newH → .ant-modal-content dostane novou výšku
317
+ - handles v portalu se přesunou s .ant-modal-content
318
+
319
+ Uživatel pustí myš
320
+
321
+ onMouseUp → removeEventListener (cleanup)
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Použití
327
+
328
+ ```jsx
329
+ // Resize zapnut (default)
330
+ <DraggableModal open={open} onCancel={onClose} title="Okno" width={800}>
331
+ obsah
332
+ </DraggableModal>
333
+
334
+ // Resize vypnut
335
+ <DraggableModal resizable={false} open={open} onCancel={onClose} title="Okno">
336
+ obsah
337
+ </DraggableModal>
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Proč ne knihovna (re-resizable, react-resizable)?
343
+
344
+ Obě fungují na principu wrapperu kolem elementu. Pro Ant Design Modal je problém, že wrapper musí obalit `.ant-modal`, ale `.ant-modal` je renderován interně knihovnou — nemáme nad ním přímou kontrolu z JSX. Ruční implementace pomocí mouse eventů je přímočařejší a nevyžaduje nové závislosti.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export default DraggableModal;
2
- declare function DraggableModal(props: any): React.ReactPortal | null;
3
- import * as React from 'react';
2
+ declare function DraggableModal(props: any): import("react/jsx-runtime").JSX.Element | null;
package/dist/index.js CHANGED
@@ -1,12 +1,21 @@
1
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable */
3
3
  import * as React from 'react';
4
4
  import { default as Modal } from 'antd/es/modal';
5
5
  import Draggable from 'react-draggable';
6
6
  import ReactDOM from 'react-dom';
7
7
  import './style.css';
8
+ const parseWidth = (w) => {
9
+ if (typeof w === 'number')
10
+ return w;
11
+ if (typeof w === 'string') {
12
+ const n = parseInt(w, 10);
13
+ return isNaN(n) ? 520 : n;
14
+ }
15
+ return 520;
16
+ };
8
17
  const DraggableModal = (props) => {
9
- const { open, onOk, onCancel, isInteractive = false, title, children, getContainer, height } = props;
18
+ const { open, onOk, onCancel, isInteractive = false, title, children, getContainer, height, resizable = true } = props;
10
19
  const draggleRef = React.useRef(null);
11
20
  const [disabled, setDisabled] = React.useState(true);
12
21
  const [bounds, setBounds] = React.useState({
@@ -15,11 +24,25 @@ const DraggableModal = (props) => {
15
24
  bottom: 0,
16
25
  right: 0,
17
26
  });
27
+ const [modalSize, setModalSize] = React.useState({
28
+ width: parseWidth(props.width),
29
+ height: null,
30
+ });
31
+ const [antModalEl, setAntModalEl] = React.useState(null);
32
+ const [dragPos, setDragPos] = React.useState({ x: 0, y: 0 });
18
33
  React.useEffect(() => {
19
34
  if (isInteractive) {
20
35
  import('./interactiveStyle.css');
21
36
  }
22
37
  }, [isInteractive]);
38
+ React.useLayoutEffect(() => {
39
+ if (resizable && open && draggleRef.current) {
40
+ setAntModalEl(draggleRef.current.querySelector('.ant-modal-content'));
41
+ }
42
+ else {
43
+ setAntModalEl(null);
44
+ }
45
+ }, [resizable, open]);
23
46
  const onStart = (_event, uiData) => {
24
47
  const { clientWidth, clientHeight } = window.document.documentElement;
25
48
  const targetRect = draggleRef.current?.getBoundingClientRect();
@@ -33,28 +56,95 @@ const DraggableModal = (props) => {
33
56
  bottom: clientHeight - (targetRect.bottom - uiData.y),
34
57
  });
35
58
  };
36
- return (open && getContainer ? (ReactDOM.createPortal(_jsx(Modal, { ...props, mask: !isInteractive, getContainer: false, zIndex: props.zIndex || 99999999, height: height, title: _jsx("div", { style: { width: '100%', cursor: 'move' }, onMouseOver: () => {
37
- if (disabled) {
38
- setDisabled(false);
39
- }
40
- }, onMouseOut: () => {
41
- setDisabled(true);
42
- }, onFocus: () => { }, onBlur: () => { }, children: title }), open: open, onOk: onOk, onCancel: onCancel, styles: {
59
+ const handleResizeStart = (e, direction) => {
60
+ e.preventDefault();
61
+ e.stopPropagation();
62
+ const modalEl = draggleRef.current?.querySelector('.ant-modal');
63
+ const contentEl = draggleRef.current?.querySelector('.ant-modal-content');
64
+ const startX = e.clientX;
65
+ const startY = e.clientY;
66
+ const startW = modalEl?.offsetWidth || modalSize.width;
67
+ const startH = contentEl?.offsetHeight || modalSize.height || 400;
68
+ const startDragX = dragPos.x;
69
+ const onMouseMove = (moveEvent) => {
70
+ const dx = moveEvent.clientX - startX;
71
+ const dy = moveEvent.clientY - startY;
72
+ setModalSize(prev => {
73
+ let newW = prev.width;
74
+ let newH = prev.height !== null ? prev.height : startH;
75
+ if (direction.includes('e'))
76
+ newW = Math.max(300, startW + dx);
77
+ if (direction.includes('w'))
78
+ newW = Math.max(300, startW - dx);
79
+ if (direction.includes('s'))
80
+ newH = Math.max(200, startH + dy);
81
+ return { width: newW, height: newH };
82
+ });
83
+ // Kompenzace: Ant Design centruje modal přes margin:auto.
84
+ // Při změně width se levý okraj posune o dx/2 — opravíme pozici Draggable.
85
+ if (direction.includes('e') || direction.includes('w')) {
86
+ setDragPos(prev => ({ ...prev, x: startDragX + dx / 2 }));
87
+ }
88
+ };
89
+ const onMouseUp = () => {
90
+ document.removeEventListener('mousemove', onMouseMove);
91
+ document.removeEventListener('mouseup', onMouseUp);
92
+ };
93
+ document.addEventListener('mousemove', onMouseMove);
94
+ document.addEventListener('mouseup', onMouseUp);
95
+ };
96
+ const resolveStyles = () => {
97
+ if (resizable && modalSize.height !== null) {
98
+ return {
99
+ body: {
100
+ paddingBottom: '30px',
101
+ height: `calc(${modalSize.height}px - 110px)`,
102
+ overflow: 'auto',
103
+ display: 'flex',
104
+ flexDirection: 'column',
105
+ boxSizing: 'border-box',
106
+ },
107
+ content: {
108
+ height: `${modalSize.height}px`,
109
+ display: 'flex',
110
+ flexDirection: 'column',
111
+ },
112
+ };
113
+ }
114
+ return {
43
115
  body: {
44
116
  paddingBottom: '30px',
45
117
  ...(height && {
46
118
  height: `calc(${height} - 110px)`,
47
119
  overflow: 'hidden',
48
120
  display: 'flex',
49
- flexDirection: 'column'
50
- })
121
+ flexDirection: 'column',
122
+ boxSizing: 'border-box',
123
+ }),
51
124
  },
52
125
  content: height ? {
53
126
  height: height,
54
127
  display: 'flex',
55
- flexDirection: 'column'
56
- } : {}
57
- }, modalRender: (modal) => (_jsx(Draggable, { disabled: disabled, bounds: bounds, onStart: (event, uiData) => onStart(event, uiData), children: _jsx("div", { ref: draggleRef, children: modal }) })), children: _jsx(_Fragment, { children: children }) }), getContainer())) : null);
128
+ flexDirection: 'column',
129
+ } : {},
130
+ };
131
+ };
132
+ const resizeHandles = (_jsxs(_Fragment, { children: [_jsx("div", { className: "drm-handle drm-handle-e", onMouseDown: (e) => handleResizeStart(e, 'e') }), _jsx("div", { className: "drm-handle drm-handle-w", onMouseDown: (e) => handleResizeStart(e, 'w') }), _jsx("div", { className: "drm-handle drm-handle-s", onMouseDown: (e) => handleResizeStart(e, 's') }), _jsx("div", { className: "drm-handle drm-handle-se", onMouseDown: (e) => handleResizeStart(e, 'se') }), _jsx("div", { className: "drm-handle drm-handle-sw", onMouseDown: (e) => handleResizeStart(e, 'sw') })] }));
133
+ // Sdílený Modal komponent
134
+ const modalContent = (_jsx(Modal, { ...props, mask: !isInteractive, getContainer: getContainer || false, zIndex: props.zIndex || 99999999, width: resizable ? modalSize.width : props.width, title: _jsx("div", { style: { width: '100%', cursor: 'move' }, onMouseOver: () => {
135
+ if (disabled) {
136
+ setDisabled(false);
137
+ }
138
+ }, onMouseOut: () => {
139
+ setDisabled(true);
140
+ }, onFocus: () => { }, onBlur: () => { }, children: title }), open: open, onOk: onOk, onCancel: onCancel, styles: resolveStyles(), modalRender: (modal) => (_jsx(Draggable, { disabled: disabled, bounds: bounds, position: dragPos, onDrag: (e, data) => setDragPos({ x: data.x, y: data.y }), onStart: (event, uiData) => onStart(event, uiData), children: _jsx("div", { ref: draggleRef, style: { position: 'relative' }, children: modal }) })), children: _jsx(_Fragment, { children: children }) }));
141
+ // Pokud je getContainer funkce, použijeme portal
142
+ // Jinak renderujeme přímo (zachová React context)
143
+ if (!open)
144
+ return null;
145
+ return (_jsxs(_Fragment, { children: [typeof getContainer === 'function'
146
+ ? ReactDOM.createPortal(modalContent, getContainer())
147
+ : modalContent, resizable && antModalEl && ReactDOM.createPortal(resizeHandles, antModalEl)] }));
58
148
  };
59
149
  export default DraggableModal;
60
150
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.jsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.jsx"],"names":[],"mappings":"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"}
package/dist/style.css CHANGED
@@ -8,3 +8,51 @@
8
8
  gap: 8px; /* Mezery mezi tlačítky */
9
9
  padding-top: 50px;
10
10
  }
11
+
12
+ /* Resize handles */
13
+ .drm-handle {
14
+ position: absolute;
15
+ z-index: 10;
16
+ user-select: none;
17
+ pointer-events: auto;
18
+ }
19
+
20
+ .drm-handle-e {
21
+ top: 12px;
22
+ right: -4px;
23
+ width: 8px;
24
+ height: calc(100% - 24px);
25
+ cursor: e-resize;
26
+ }
27
+
28
+ .drm-handle-w {
29
+ top: 12px;
30
+ left: -4px;
31
+ width: 8px;
32
+ height: calc(100% - 24px);
33
+ cursor: w-resize;
34
+ }
35
+
36
+ .drm-handle-s {
37
+ bottom: -4px;
38
+ left: 12px;
39
+ width: calc(100% - 24px);
40
+ height: 8px;
41
+ cursor: s-resize;
42
+ }
43
+
44
+ .drm-handle-se {
45
+ bottom: -4px;
46
+ right: -4px;
47
+ width: 16px;
48
+ height: 16px;
49
+ cursor: se-resize;
50
+ }
51
+
52
+ .drm-handle-sw {
53
+ bottom: -4px;
54
+ left: -4px;
55
+ width: 16px;
56
+ height: 16px;
57
+ cursor: sw-resize;
58
+ }
package/index.jsx CHANGED
@@ -5,9 +5,14 @@ import Draggable from 'react-draggable';
5
5
  import ReactDOM from 'react-dom';
6
6
  import './style.css';
7
7
 
8
+ const parseWidth = (w) => {
9
+ if (typeof w === 'number') return w;
10
+ if (typeof w === 'string') { const n = parseInt(w, 10); return isNaN(n) ? 520 : n; }
11
+ return 520;
12
+ };
8
13
 
9
14
  const DraggableModal = (props) => {
10
- const { open, onOk, onCancel, isInteractive = false, title, children, getContainer, height } = props;
15
+ const { open, onOk, onCancel, isInteractive = false, title, children, getContainer, height, resizable = true } = props;
11
16
  const draggleRef = React.useRef(null);
12
17
  const [disabled, setDisabled] = React.useState(true);
13
18
  const [bounds, setBounds] = React.useState({
@@ -16,6 +21,12 @@ const DraggableModal = (props) => {
16
21
  bottom: 0,
17
22
  right: 0,
18
23
  });
24
+ const [modalSize, setModalSize] = React.useState({
25
+ width: parseWidth(props.width),
26
+ height: null,
27
+ });
28
+ const [antModalEl, setAntModalEl] = React.useState(null);
29
+ const [dragPos, setDragPos] = React.useState({ x: 0, y: 0 });
19
30
 
20
31
  React.useEffect(() => {
21
32
  if (isInteractive) {
@@ -23,6 +34,14 @@ const DraggableModal = (props) => {
23
34
  }
24
35
  }, [isInteractive]);
25
36
 
37
+ React.useLayoutEffect(() => {
38
+ if (resizable && open && draggleRef.current) {
39
+ setAntModalEl(draggleRef.current.querySelector('.ant-modal-content'));
40
+ } else {
41
+ setAntModalEl(null);
42
+ }
43
+ }, [resizable, open]);
44
+
26
45
  const onStart = (_event, uiData) => {
27
46
  const { clientWidth, clientHeight } = window.document.documentElement;
28
47
  const targetRect = draggleRef.current?.getBoundingClientRect();
@@ -37,67 +56,156 @@ const DraggableModal = (props) => {
37
56
  });
38
57
  };
39
58
 
40
- return (
41
- open && getContainer ? (
42
- ReactDOM.createPortal(
43
- <Modal
44
- {...props}
45
- mask={!isInteractive}
46
- getContainer={false} // Zajistí, že modal bude přímo vložen do portálu
47
- zIndex={props.zIndex || 99999999}
48
- height={height}
49
- title={
50
- <div
51
- style={{ width: '100%', cursor: 'move' }}
52
- onMouseOver={() => {
53
- if (disabled) {
54
- setDisabled(false);
55
- }
56
- }}
57
- onMouseOut={() => {
58
- setDisabled(true);
59
- }}
60
- onFocus={() => { }}
61
- onBlur={() => { }}
62
- >
63
- {title}
64
- </div>
65
- }
66
- open={open}
67
- onOk={onOk}
68
- onCancel={onCancel}
69
- styles={{
70
- body: {
71
- paddingBottom: '30px',
72
- ...(height && {
73
- height: `calc(${height} - 110px)`,
74
- overflow: 'hidden',
75
- display: 'flex',
76
- flexDirection: 'column'
77
- })
78
- },
79
- content: height ? {
80
- height: height,
81
- display: 'flex',
82
- flexDirection: 'column'
83
- } : {}
59
+ const handleResizeStart = (e, direction) => {
60
+ e.preventDefault();
61
+ e.stopPropagation();
62
+
63
+ const modalEl = draggleRef.current?.querySelector('.ant-modal');
64
+ const contentEl = draggleRef.current?.querySelector('.ant-modal-content');
65
+ const startX = e.clientX;
66
+ const startY = e.clientY;
67
+ const startW = modalEl?.offsetWidth || modalSize.width;
68
+ const startH = contentEl?.offsetHeight || modalSize.height || 400;
69
+ const startDragX = dragPos.x;
70
+
71
+ const onMouseMove = (moveEvent) => {
72
+ const dx = moveEvent.clientX - startX;
73
+ const dy = moveEvent.clientY - startY;
74
+
75
+ setModalSize(prev => {
76
+ let newW = prev.width;
77
+ let newH = prev.height !== null ? prev.height : startH;
78
+
79
+ if (direction.includes('e')) newW = Math.max(300, startW + dx);
80
+ if (direction.includes('w')) newW = Math.max(300, startW - dx);
81
+ if (direction.includes('s')) newH = Math.max(200, startH + dy);
82
+
83
+ return { width: newW, height: newH };
84
+ });
85
+
86
+ // Kompenzace: Ant Design centruje modal přes margin:auto.
87
+ // Při změně width se levý okraj posune o dx/2 — opravíme pozici Draggable.
88
+ if (direction.includes('e') || direction.includes('w')) {
89
+ setDragPos(prev => ({ ...prev, x: startDragX + dx / 2 }));
90
+ }
91
+ };
92
+
93
+ const onMouseUp = () => {
94
+ document.removeEventListener('mousemove', onMouseMove);
95
+ document.removeEventListener('mouseup', onMouseUp);
96
+ };
97
+
98
+ document.addEventListener('mousemove', onMouseMove);
99
+ document.addEventListener('mouseup', onMouseUp);
100
+ };
101
+
102
+ const resolveStyles = () => {
103
+ if (resizable && modalSize.height !== null) {
104
+ return {
105
+ body: {
106
+ paddingBottom: '30px',
107
+ height: `calc(${modalSize.height}px - 110px)`,
108
+ overflow: 'auto',
109
+ display: 'flex',
110
+ flexDirection: 'column',
111
+ boxSizing: 'border-box',
112
+ },
113
+ content: {
114
+ height: `${modalSize.height}px`,
115
+ display: 'flex',
116
+ flexDirection: 'column',
117
+ },
118
+ };
119
+ }
120
+
121
+ return {
122
+ body: {
123
+ paddingBottom: '30px',
124
+ ...(height && {
125
+ height: `calc(${height} - 110px)`,
126
+ overflow: 'hidden',
127
+ display: 'flex',
128
+ flexDirection: 'column',
129
+ boxSizing: 'border-box',
130
+ }),
131
+ },
132
+ content: height ? {
133
+ height: height,
134
+ display: 'flex',
135
+ flexDirection: 'column',
136
+ } : {},
137
+ };
138
+ };
139
+
140
+ const resizeHandles = (
141
+ <>
142
+ <div className="drm-handle drm-handle-e" onMouseDown={(e) => handleResizeStart(e, 'e')} />
143
+ <div className="drm-handle drm-handle-w" onMouseDown={(e) => handleResizeStart(e, 'w')} />
144
+ <div className="drm-handle drm-handle-s" onMouseDown={(e) => handleResizeStart(e, 's')} />
145
+ <div className="drm-handle drm-handle-se" onMouseDown={(e) => handleResizeStart(e, 'se')} />
146
+ <div className="drm-handle drm-handle-sw" onMouseDown={(e) => handleResizeStart(e, 'sw')} />
147
+ </>
148
+ );
149
+
150
+ // Sdílený Modal komponent
151
+ const modalContent = (
152
+ <Modal
153
+ {...props}
154
+ mask={!isInteractive}
155
+ getContainer={getContainer || false} // false = renderovat v místě
156
+ zIndex={props.zIndex || 99999999}
157
+ width={resizable ? modalSize.width : props.width}
158
+ title={
159
+ <div
160
+ style={{ width: '100%', cursor: 'move' }}
161
+ onMouseOver={() => {
162
+ if (disabled) {
163
+ setDisabled(false);
164
+ }
84
165
  }}
85
- modalRender={(modal) => (
86
- <Draggable
87
- disabled={disabled}
88
- bounds={bounds}
89
- onStart={(event, uiData) => onStart(event, uiData)}
90
- >
91
- <div ref={draggleRef}>{modal}</div>
92
- </Draggable>
93
- )}
166
+ onMouseOut={() => {
167
+ setDisabled(true);
168
+ }}
169
+ onFocus={() => { }}
170
+ onBlur={() => { }}
171
+ >
172
+ {title}
173
+ </div>
174
+ }
175
+ open={open}
176
+ onOk={onOk}
177
+ onCancel={onCancel}
178
+ styles={resolveStyles()}
179
+ modalRender={(modal) => (
180
+ <Draggable
181
+ disabled={disabled}
182
+ bounds={bounds}
183
+ position={dragPos}
184
+ onDrag={(e, data) => setDragPos({ x: data.x, y: data.y })}
185
+ onStart={(event, uiData) => onStart(event, uiData)}
94
186
  >
95
- <>{children}</>
96
- </Modal>,
97
- getContainer()
98
- )
99
- ) : null
187
+ <div ref={draggleRef} style={{ position: 'relative' }}>
188
+ {modal}
189
+ </div>
190
+ </Draggable>
191
+ )}
192
+ >
193
+ <>{children}</>
194
+ </Modal>
195
+ );
196
+
197
+ // Pokud je getContainer funkce, použijeme portal
198
+ // Jinak renderujeme přímo (zachová React context)
199
+ if (!open) return null;
200
+
201
+ return (
202
+ <>
203
+ {typeof getContainer === 'function'
204
+ ? ReactDOM.createPortal(modalContent, getContainer())
205
+ : modalContent}
206
+ {resizable && antModalEl && ReactDOM.createPortal(resizeHandles, antModalEl)}
207
+ </>
100
208
  );
101
209
  };
102
210
 
103
- export default DraggableModal;
211
+ export default DraggableModal;
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@bit.rhplus/draggable-modal",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "homepage": "https://bit.cloud/remote-scope/draggable-modal",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "remote-scope",
8
8
  "name": "draggable-modal",
9
- "version": "0.0.12"
9
+ "version": "0.0.14"
10
10
  },
11
11
  "dependencies": {
12
12
  "antd": "^5.20.6",
13
13
  "react-draggable": "^4.4.6"
14
14
  },
15
15
  "devDependencies": {
16
- "@teambit/react.react-env": "1.0.132"
16
+ "@teambit/react.react-env": "1.3.1"
17
17
  },
18
18
  "peerDependencies": {
19
- "react": "^17.0.0 || ^18.0.0",
20
- "react-dom": "^17.0.0 || ^18.0.0"
19
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
20
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
21
21
  },
22
22
  "license": "SEE LICENSE IN UNLICENSED",
23
23
  "optionalDependencies": {},
package/style.css CHANGED
@@ -8,3 +8,51 @@
8
8
  gap: 8px; /* Mezery mezi tlačítky */
9
9
  padding-top: 50px;
10
10
  }
11
+
12
+ /* Resize handles */
13
+ .drm-handle {
14
+ position: absolute;
15
+ z-index: 10;
16
+ user-select: none;
17
+ pointer-events: auto;
18
+ }
19
+
20
+ .drm-handle-e {
21
+ top: 12px;
22
+ right: -4px;
23
+ width: 8px;
24
+ height: calc(100% - 24px);
25
+ cursor: e-resize;
26
+ }
27
+
28
+ .drm-handle-w {
29
+ top: 12px;
30
+ left: -4px;
31
+ width: 8px;
32
+ height: calc(100% - 24px);
33
+ cursor: w-resize;
34
+ }
35
+
36
+ .drm-handle-s {
37
+ bottom: -4px;
38
+ left: 12px;
39
+ width: calc(100% - 24px);
40
+ height: 8px;
41
+ cursor: s-resize;
42
+ }
43
+
44
+ .drm-handle-se {
45
+ bottom: -4px;
46
+ right: -4px;
47
+ width: 16px;
48
+ height: 16px;
49
+ cursor: se-resize;
50
+ }
51
+
52
+ .drm-handle-sw {
53
+ bottom: -4px;
54
+ left: -4px;
55
+ width: 16px;
56
+ height: 16px;
57
+ cursor: sw-resize;
58
+ }