@bit.rhplus/draggable-modal 0.0.13 → 0.0.15

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.js CHANGED
@@ -1,12 +1,30 @@
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
+ };
17
+ // Wrapper – podmíněné renderování = destroy on close (kompletní unmount při zavření)
8
18
  const DraggableModal = (props) => {
9
- const { open, onOk, onCancel, isInteractive = false, title, children, getContainer, height } = props;
19
+ if (!props.open)
20
+ return null;
21
+ return _jsx(DraggableModalInner, { ...props });
22
+ };
23
+ // Inner komponenta – obsahuje veškerou logiku a stav
24
+ // Při zavření se kompletně unmountuje → všechny hooks a stav jsou zničeny
25
+ // Při otevření se vytvoří čerstvá instance → žádný starý stav
26
+ const DraggableModalInner = (props) => {
27
+ const { open, onOk, onCancel, isInteractive = false, title, children, getContainer, height, resizable = true } = props;
10
28
  const draggleRef = React.useRef(null);
11
29
  const [disabled, setDisabled] = React.useState(true);
12
30
  const [bounds, setBounds] = React.useState({
@@ -15,11 +33,25 @@ const DraggableModal = (props) => {
15
33
  bottom: 0,
16
34
  right: 0,
17
35
  });
36
+ const [modalSize, setModalSize] = React.useState({
37
+ width: parseWidth(props.width),
38
+ height: null,
39
+ });
40
+ const [antModalEl, setAntModalEl] = React.useState(null);
41
+ const [dragPos, setDragPos] = React.useState({ x: 0, y: 0 });
18
42
  React.useEffect(() => {
19
43
  if (isInteractive) {
20
44
  import('./interactiveStyle.css');
21
45
  }
22
46
  }, [isInteractive]);
47
+ React.useLayoutEffect(() => {
48
+ if (resizable && open && draggleRef.current) {
49
+ setAntModalEl(draggleRef.current.querySelector('.ant-modal-content'));
50
+ }
51
+ else {
52
+ setAntModalEl(null);
53
+ }
54
+ }, [resizable, open]);
23
55
  const onStart = (_event, uiData) => {
24
56
  const { clientWidth, clientHeight } = window.document.documentElement;
25
57
  const targetRect = draggleRef.current?.getBoundingClientRect();
@@ -33,32 +65,93 @@ const DraggableModal = (props) => {
33
65
  bottom: clientHeight - (targetRect.bottom - uiData.y),
34
66
  });
35
67
  };
36
- // Sdílený Modal komponent
37
- const modalContent = (_jsx(Modal, { ...props, mask: !isInteractive, getContainer: getContainer || false, zIndex: props.zIndex || 99999999, height: height, title: _jsx("div", { style: { width: '100%', cursor: 'move' }, onMouseOver: () => {
38
- if (disabled) {
39
- setDisabled(false);
40
- }
41
- }, onMouseOut: () => {
42
- setDisabled(true);
43
- }, onFocus: () => { }, onBlur: () => { }, children: title }), open: open, onOk: onOk, onCancel: onCancel, styles: {
68
+ const handleResizeStart = (e, direction) => {
69
+ e.preventDefault();
70
+ e.stopPropagation();
71
+ const modalEl = draggleRef.current?.querySelector('.ant-modal');
72
+ const contentEl = draggleRef.current?.querySelector('.ant-modal-content');
73
+ const startX = e.clientX;
74
+ const startY = e.clientY;
75
+ const startW = modalEl?.offsetWidth || modalSize.width;
76
+ const startH = contentEl?.offsetHeight || modalSize.height || 400;
77
+ const startDragX = dragPos.x;
78
+ const onMouseMove = (moveEvent) => {
79
+ const dx = moveEvent.clientX - startX;
80
+ const dy = moveEvent.clientY - startY;
81
+ setModalSize(prev => {
82
+ let newW = prev.width;
83
+ let newH = prev.height !== null ? prev.height : startH;
84
+ if (direction.includes('e'))
85
+ newW = Math.max(300, startW + dx);
86
+ if (direction.includes('w'))
87
+ newW = Math.max(300, startW - dx);
88
+ if (direction.includes('s'))
89
+ newH = Math.max(200, startH + dy);
90
+ return { width: newW, height: newH };
91
+ });
92
+ // Kompenzace: Ant Design centruje modal přes margin:auto.
93
+ // Při změně width se levý okraj posune o dx/2 — opravíme pozici Draggable.
94
+ if (direction.includes('e') || direction.includes('w')) {
95
+ setDragPos(prev => ({ ...prev, x: startDragX + dx / 2 }));
96
+ }
97
+ };
98
+ const onMouseUp = () => {
99
+ document.removeEventListener('mousemove', onMouseMove);
100
+ document.removeEventListener('mouseup', onMouseUp);
101
+ };
102
+ document.addEventListener('mousemove', onMouseMove);
103
+ document.addEventListener('mouseup', onMouseUp);
104
+ };
105
+ const resolveStyles = () => {
106
+ if (resizable && modalSize.height !== null) {
107
+ return {
108
+ body: {
109
+ paddingBottom: '30px',
110
+ height: `calc(${modalSize.height}px - 110px)`,
111
+ overflow: 'auto',
112
+ display: 'flex',
113
+ flexDirection: 'column',
114
+ boxSizing: 'border-box',
115
+ },
116
+ content: {
117
+ height: `${modalSize.height}px`,
118
+ display: 'flex',
119
+ flexDirection: 'column',
120
+ },
121
+ };
122
+ }
123
+ return {
44
124
  body: {
45
125
  paddingBottom: '30px',
46
126
  ...(height && {
47
127
  height: `calc(${height} - 110px)`,
48
128
  overflow: 'hidden',
49
129
  display: 'flex',
50
- flexDirection: 'column'
51
- })
130
+ flexDirection: 'column',
131
+ boxSizing: 'border-box',
132
+ }),
52
133
  },
53
134
  content: height ? {
54
135
  height: height,
55
136
  display: 'flex',
56
- flexDirection: 'column'
57
- } : {}
58
- }, 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 }) }));
137
+ flexDirection: 'column',
138
+ } : {},
139
+ };
140
+ };
141
+ 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') })] }));
142
+ // Sdílený Modal komponent
143
+ 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: () => {
144
+ if (disabled) {
145
+ setDisabled(false);
146
+ }
147
+ }, onMouseOut: () => {
148
+ setDisabled(true);
149
+ }, 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 }) }));
59
150
  // Pokud je getContainer funkce, použijeme portal
60
151
  // Jinak renderujeme přímo (zachová React context)
61
- return open ? (typeof getContainer === 'function' ? (ReactDOM.createPortal(modalContent, getContainer())) : (modalContent)) : null;
152
+ return (_jsxs(_Fragment, { children: [typeof getContainer === 'function'
153
+ ? ReactDOM.createPortal(modalContent, getContainer())
154
+ : modalContent, resizable && antModalEl && ReactDOM.createPortal(resizeHandles, antModalEl)] }));
62
155
  };
63
156
  export default DraggableModal;
64
157
  //# 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,23 @@ 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
 
14
+ // Wrapper – podmíněné renderování = destroy on close (kompletní unmount při zavření)
9
15
  const DraggableModal = (props) => {
10
- const { open, onOk, onCancel, isInteractive = false, title, children, getContainer, height } = props;
16
+ if (!props.open) return null;
17
+ return <DraggableModalInner {...props} />;
18
+ };
19
+
20
+ // Inner komponenta – obsahuje veškerou logiku a stav
21
+ // Při zavření se kompletně unmountuje → všechny hooks a stav jsou zničeny
22
+ // Při otevření se vytvoří čerstvá instance → žádný starý stav
23
+ const DraggableModalInner = (props) => {
24
+ const { open, onOk, onCancel, isInteractive = false, title, children, getContainer, height, resizable = true } = props;
11
25
  const draggleRef = React.useRef(null);
12
26
  const [disabled, setDisabled] = React.useState(true);
13
27
  const [bounds, setBounds] = React.useState({
@@ -16,6 +30,12 @@ const DraggableModal = (props) => {
16
30
  bottom: 0,
17
31
  right: 0,
18
32
  });
33
+ const [modalSize, setModalSize] = React.useState({
34
+ width: parseWidth(props.width),
35
+ height: null,
36
+ });
37
+ const [antModalEl, setAntModalEl] = React.useState(null);
38
+ const [dragPos, setDragPos] = React.useState({ x: 0, y: 0 });
19
39
 
20
40
  React.useEffect(() => {
21
41
  if (isInteractive) {
@@ -23,6 +43,14 @@ const DraggableModal = (props) => {
23
43
  }
24
44
  }, [isInteractive]);
25
45
 
46
+ React.useLayoutEffect(() => {
47
+ if (resizable && open && draggleRef.current) {
48
+ setAntModalEl(draggleRef.current.querySelector('.ant-modal-content'));
49
+ } else {
50
+ setAntModalEl(null);
51
+ }
52
+ }, [resizable, open]);
53
+
26
54
  const onStart = (_event, uiData) => {
27
55
  const { clientWidth, clientHeight } = window.document.documentElement;
28
56
  const targetRect = draggleRef.current?.getBoundingClientRect();
@@ -37,6 +65,97 @@ const DraggableModal = (props) => {
37
65
  });
38
66
  };
39
67
 
68
+ const handleResizeStart = (e, direction) => {
69
+ e.preventDefault();
70
+ e.stopPropagation();
71
+
72
+ const modalEl = draggleRef.current?.querySelector('.ant-modal');
73
+ const contentEl = draggleRef.current?.querySelector('.ant-modal-content');
74
+ const startX = e.clientX;
75
+ const startY = e.clientY;
76
+ const startW = modalEl?.offsetWidth || modalSize.width;
77
+ const startH = contentEl?.offsetHeight || modalSize.height || 400;
78
+ const startDragX = dragPos.x;
79
+
80
+ const onMouseMove = (moveEvent) => {
81
+ const dx = moveEvent.clientX - startX;
82
+ const dy = moveEvent.clientY - startY;
83
+
84
+ setModalSize(prev => {
85
+ let newW = prev.width;
86
+ let newH = prev.height !== null ? prev.height : startH;
87
+
88
+ if (direction.includes('e')) newW = Math.max(300, startW + dx);
89
+ if (direction.includes('w')) newW = Math.max(300, startW - dx);
90
+ if (direction.includes('s')) newH = Math.max(200, startH + dy);
91
+
92
+ return { width: newW, height: newH };
93
+ });
94
+
95
+ // Kompenzace: Ant Design centruje modal přes margin:auto.
96
+ // Při změně width se levý okraj posune o dx/2 — opravíme pozici Draggable.
97
+ if (direction.includes('e') || direction.includes('w')) {
98
+ setDragPos(prev => ({ ...prev, x: startDragX + dx / 2 }));
99
+ }
100
+ };
101
+
102
+ const onMouseUp = () => {
103
+ document.removeEventListener('mousemove', onMouseMove);
104
+ document.removeEventListener('mouseup', onMouseUp);
105
+ };
106
+
107
+ document.addEventListener('mousemove', onMouseMove);
108
+ document.addEventListener('mouseup', onMouseUp);
109
+ };
110
+
111
+ const resolveStyles = () => {
112
+ if (resizable && modalSize.height !== null) {
113
+ return {
114
+ body: {
115
+ paddingBottom: '30px',
116
+ height: `calc(${modalSize.height}px - 110px)`,
117
+ overflow: 'auto',
118
+ display: 'flex',
119
+ flexDirection: 'column',
120
+ boxSizing: 'border-box',
121
+ },
122
+ content: {
123
+ height: `${modalSize.height}px`,
124
+ display: 'flex',
125
+ flexDirection: 'column',
126
+ },
127
+ };
128
+ }
129
+
130
+ return {
131
+ body: {
132
+ paddingBottom: '30px',
133
+ ...(height && {
134
+ height: `calc(${height} - 110px)`,
135
+ overflow: 'hidden',
136
+ display: 'flex',
137
+ flexDirection: 'column',
138
+ boxSizing: 'border-box',
139
+ }),
140
+ },
141
+ content: height ? {
142
+ height: height,
143
+ display: 'flex',
144
+ flexDirection: 'column',
145
+ } : {},
146
+ };
147
+ };
148
+
149
+ const resizeHandles = (
150
+ <>
151
+ <div className="drm-handle drm-handle-e" onMouseDown={(e) => handleResizeStart(e, 'e')} />
152
+ <div className="drm-handle drm-handle-w" onMouseDown={(e) => handleResizeStart(e, 'w')} />
153
+ <div className="drm-handle drm-handle-s" onMouseDown={(e) => handleResizeStart(e, 's')} />
154
+ <div className="drm-handle drm-handle-se" onMouseDown={(e) => handleResizeStart(e, 'se')} />
155
+ <div className="drm-handle drm-handle-sw" onMouseDown={(e) => handleResizeStart(e, 'sw')} />
156
+ </>
157
+ );
158
+
40
159
  // Sdílený Modal komponent
41
160
  const modalContent = (
42
161
  <Modal
@@ -44,7 +163,7 @@ const DraggableModal = (props) => {
44
163
  mask={!isInteractive}
45
164
  getContainer={getContainer || false} // false = renderovat v místě
46
165
  zIndex={props.zIndex || 99999999}
47
- height={height}
166
+ width={resizable ? modalSize.width : props.width}
48
167
  title={
49
168
  <div
50
169
  style={{ width: '100%', cursor: 'move' }}
@@ -65,29 +184,18 @@ const DraggableModal = (props) => {
65
184
  open={open}
66
185
  onOk={onOk}
67
186
  onCancel={onCancel}
68
- styles={{
69
- body: {
70
- paddingBottom: '30px',
71
- ...(height && {
72
- height: `calc(${height} - 110px)`,
73
- overflow: 'hidden',
74
- display: 'flex',
75
- flexDirection: 'column'
76
- })
77
- },
78
- content: height ? {
79
- height: height,
80
- display: 'flex',
81
- flexDirection: 'column'
82
- } : {}
83
- }}
187
+ styles={resolveStyles()}
84
188
  modalRender={(modal) => (
85
189
  <Draggable
86
190
  disabled={disabled}
87
191
  bounds={bounds}
192
+ position={dragPos}
193
+ onDrag={(e, data) => setDragPos({ x: data.x, y: data.y })}
88
194
  onStart={(event, uiData) => onStart(event, uiData)}
89
195
  >
90
- <div ref={draggleRef}>{modal}</div>
196
+ <div ref={draggleRef} style={{ position: 'relative' }}>
197
+ {modal}
198
+ </div>
91
199
  </Draggable>
92
200
  )}
93
201
  >
@@ -97,13 +205,14 @@ const DraggableModal = (props) => {
97
205
 
98
206
  // Pokud je getContainer funkce, použijeme portal
99
207
  // Jinak renderujeme přímo (zachová React context)
100
- return open ? (
101
- typeof getContainer === 'function' ? (
102
- ReactDOM.createPortal(modalContent, getContainer())
103
- ) : (
104
- modalContent
105
- )
106
- ) : null;
208
+ return (
209
+ <>
210
+ {typeof getContainer === 'function'
211
+ ? ReactDOM.createPortal(modalContent, getContainer())
212
+ : modalContent}
213
+ {resizable && antModalEl && ReactDOM.createPortal(resizeHandles, antModalEl)}
214
+ </>
215
+ );
107
216
  };
108
217
 
109
- export default DraggableModal;
218
+ export default DraggableModal;
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@bit.rhplus/draggable-modal",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
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.13"
9
+ "version": "0.0.15"
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
+ }