rails_modal_manager 1.0.40 → 1.0.41
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.
- checksums.yaml +4 -4
- data/app/javascript/rails_modal_manager/controllers/rmm_header_controller.js +100 -1
- data/app/javascript/rails_modal_manager/controllers/rmm_taskbar_controller.js +86 -0
- data/app/javascript/rails_modal_manager/modal_store.js +17 -1
- data/lib/rails_modal_manager/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5884364bb07444f45c08e4733c5623883a775592ba853a1275f5f2bd84bb5c03
|
|
4
|
+
data.tar.gz: 06a78be76359c81f55ec9e904488a1f3d961fdababf4bb8b31c24d0d19a8b7e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 985fca2add00de7fca1c9c5d05d66be9b8a6dbc208526de6811dad2c4b27977be9aa332dce1287d561d0fe04c3ec365768abbeeac89f582f07af8250aed843e2
|
|
7
|
+
data.tar.gz: ab10ad1ad3a86cd5a6ad64af536d015cbfeccc030523e03c980c74cb042cab25bd70bd936cd7247980097a18140d2c0969f18c9afb6c6df8fc66301c959c4232
|
|
@@ -11,7 +11,7 @@ export default class extends Controller {
|
|
|
11
11
|
defaultSize: { type: String, default: "md" },
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
static targets = ["maximizeBtn", "sidebarToggle"]
|
|
14
|
+
static targets = ["maximizeBtn", "sidebarToggle", "title"]
|
|
15
15
|
|
|
16
16
|
connect() {
|
|
17
17
|
// Listen for sidebar toggled event to update icon
|
|
@@ -20,6 +20,17 @@ export default class extends Controller {
|
|
|
20
20
|
if (modal) {
|
|
21
21
|
modal.addEventListener('rmm-sidebar:toggled', this.handleSidebarToggled)
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
// Title 더블클릭 이벤트 리스너 추가
|
|
25
|
+
this.setupTitleEdit()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setupTitleEdit() {
|
|
29
|
+
const titleEl = this.element.querySelector('.rmm-header-title')
|
|
30
|
+
if (titleEl) {
|
|
31
|
+
titleEl.addEventListener('dblclick', (e) => this.editTitle(e))
|
|
32
|
+
titleEl.style.cursor = 'default'
|
|
33
|
+
}
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
disconnect() {
|
|
@@ -173,4 +184,92 @@ export default class extends Controller {
|
|
|
173
184
|
if (hiddenIcon) hiddenIcon.style.display = 'none'
|
|
174
185
|
}
|
|
175
186
|
}
|
|
187
|
+
|
|
188
|
+
// ============================================
|
|
189
|
+
// Title Edit (Double-click to edit)
|
|
190
|
+
// ============================================
|
|
191
|
+
|
|
192
|
+
editTitle(e) {
|
|
193
|
+
e.stopPropagation()
|
|
194
|
+
e.preventDefault()
|
|
195
|
+
|
|
196
|
+
const titleEl = e.currentTarget
|
|
197
|
+
if (!titleEl) return
|
|
198
|
+
|
|
199
|
+
// 이미 수정 중인 경우 무시
|
|
200
|
+
if (titleEl.querySelector('input')) return
|
|
201
|
+
|
|
202
|
+
const currentTitle = titleEl.textContent.trim()
|
|
203
|
+
|
|
204
|
+
// input 생성
|
|
205
|
+
const input = document.createElement('input')
|
|
206
|
+
input.type = 'text'
|
|
207
|
+
input.value = currentTitle
|
|
208
|
+
input.className = 'rmm-header-title-input'
|
|
209
|
+
input.style.cssText = `
|
|
210
|
+
width: 100%;
|
|
211
|
+
min-width: 100px;
|
|
212
|
+
max-width: 300px;
|
|
213
|
+
padding: 2px 6px;
|
|
214
|
+
border: 1px solid rgba(255,255,255,0.5);
|
|
215
|
+
border-radius: 4px;
|
|
216
|
+
font-size: inherit;
|
|
217
|
+
font-weight: inherit;
|
|
218
|
+
font-family: inherit;
|
|
219
|
+
background: rgba(0,0,0,0.2);
|
|
220
|
+
color: inherit;
|
|
221
|
+
outline: none;
|
|
222
|
+
`
|
|
223
|
+
|
|
224
|
+
// title 내용을 input으로 교체
|
|
225
|
+
titleEl.textContent = ''
|
|
226
|
+
titleEl.appendChild(input)
|
|
227
|
+
input.focus()
|
|
228
|
+
input.select()
|
|
229
|
+
|
|
230
|
+
// 저장 함수
|
|
231
|
+
const saveTitle = () => {
|
|
232
|
+
const newTitle = input.value.trim()
|
|
233
|
+
const finalTitle = newTitle || currentTitle
|
|
234
|
+
|
|
235
|
+
// title 업데이트
|
|
236
|
+
titleEl.textContent = finalTitle
|
|
237
|
+
|
|
238
|
+
// modalStore의 title도 업데이트
|
|
239
|
+
if (newTitle && newTitle !== currentTitle) {
|
|
240
|
+
this.updateModalTitle(finalTitle)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// blur 시 저장
|
|
245
|
+
input.addEventListener('blur', saveTitle, { once: true })
|
|
246
|
+
|
|
247
|
+
// Enter 시 저장, Escape 시 취소
|
|
248
|
+
input.addEventListener('keydown', (ke) => {
|
|
249
|
+
if (ke.key === 'Enter') {
|
|
250
|
+
ke.preventDefault()
|
|
251
|
+
input.blur()
|
|
252
|
+
} else if (ke.key === 'Escape') {
|
|
253
|
+
ke.preventDefault()
|
|
254
|
+
input.removeEventListener('blur', saveTitle)
|
|
255
|
+
titleEl.textContent = currentTitle
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
updateModalTitle(newTitle) {
|
|
261
|
+
const modal = this.getModalElement()
|
|
262
|
+
if (!modal) return
|
|
263
|
+
|
|
264
|
+
const modalId = modal.id || this.modalIdValue
|
|
265
|
+
if (!modalId) return
|
|
266
|
+
|
|
267
|
+
// modalStore에서 title 업데이트
|
|
268
|
+
import("rails_modal_manager/modal_store").then(module => {
|
|
269
|
+
const modalStore = module.default
|
|
270
|
+
if (modalStore && modalStore.updateModal) {
|
|
271
|
+
modalStore.updateModal(modalId, { title: newTitle })
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
}
|
|
176
275
|
}
|
|
@@ -69,9 +69,21 @@ export default class extends Controller {
|
|
|
69
69
|
const closeAllBtn = groups.length >= 2 ? this.renderCloseAllButton() : ''
|
|
70
70
|
const items = groups.map((group, index) => this.renderTaskbarItem(group, index)).join('')
|
|
71
71
|
this.itemsTarget.innerHTML = closeAllBtn + items
|
|
72
|
+
|
|
73
|
+
// 복구된 상태의 아이템에 더블클릭 이벤트 직접 추가
|
|
74
|
+
this.attachTitleEditListeners()
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
attachTitleEditListeners() {
|
|
79
|
+
const disabledItems = this.element.querySelectorAll('.rmm-taskbar-item-disabled')
|
|
80
|
+
disabledItems.forEach(item => {
|
|
81
|
+
// 기존 리스너 제거를 위해 새 함수 참조 저장
|
|
82
|
+
item._dblclickHandler = (e) => this.editTitle(e, item)
|
|
83
|
+
item.addEventListener('dblclick', item._dblclickHandler)
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
renderCloseAllButton() {
|
|
76
88
|
return `
|
|
77
89
|
<button type="button" class="rmm-taskbar-close-all" data-action="click->rmm-taskbar#closeAll" title="최소화된 모달 모두 닫기">
|
|
@@ -89,6 +101,7 @@ export default class extends Controller {
|
|
|
89
101
|
const disabledClass = isRestored ? ' rmm-taskbar-item-disabled' : ''
|
|
90
102
|
|
|
91
103
|
// 드래그 이벤트 + 클릭 이벤트 (복구된 상태가 아닐 때만 클릭 이벤트 추가)
|
|
104
|
+
// 복구된 상태에서는 더블클릭으로 title 수정 가능 (attachTitleEditListeners에서 처리)
|
|
92
105
|
const dragEvents = 'dragstart->rmm-taskbar#dragStart dragend->rmm-taskbar#dragEnd dragover->rmm-taskbar#dragOver drop->rmm-taskbar#drop dragenter->rmm-taskbar#dragEnter dragleave->rmm-taskbar#dragLeave'
|
|
93
106
|
const clickEvent = isRestored ? '' : ' click->rmm-taskbar#restoreItem'
|
|
94
107
|
const dataAction = `data-action="${dragEvents}${clickEvent}"`
|
|
@@ -270,6 +283,79 @@ export default class extends Controller {
|
|
|
270
283
|
}
|
|
271
284
|
}
|
|
272
285
|
|
|
286
|
+
// ============================================
|
|
287
|
+
// Title Edit (Double-click on disabled/restored item)
|
|
288
|
+
// ============================================
|
|
289
|
+
|
|
290
|
+
editTitle(e, itemElement = null) {
|
|
291
|
+
e.stopPropagation()
|
|
292
|
+
e.preventDefault()
|
|
293
|
+
|
|
294
|
+
const item = itemElement || e.currentTarget
|
|
295
|
+
const titleSpan = item.querySelector('.rmm-taskbar-item-title')
|
|
296
|
+
const rootModalId = item.dataset.rootId
|
|
297
|
+
|
|
298
|
+
if (!titleSpan || !rootModalId) return
|
|
299
|
+
|
|
300
|
+
// 이미 수정 중인 경우 무시
|
|
301
|
+
if (titleSpan.querySelector('input')) return
|
|
302
|
+
|
|
303
|
+
const currentTitle = titleSpan.textContent
|
|
304
|
+
const originalWidth = titleSpan.offsetWidth
|
|
305
|
+
|
|
306
|
+
// input 생성
|
|
307
|
+
const input = document.createElement('input')
|
|
308
|
+
input.type = 'text'
|
|
309
|
+
input.value = currentTitle
|
|
310
|
+
input.className = 'rmm-taskbar-title-input'
|
|
311
|
+
input.style.cssText = `
|
|
312
|
+
width: ${Math.max(originalWidth, 80)}px;
|
|
313
|
+
min-width: 80px;
|
|
314
|
+
max-width: 200px;
|
|
315
|
+
padding: 2px 4px;
|
|
316
|
+
border: 1px solid #60a5fa;
|
|
317
|
+
border-radius: 3px;
|
|
318
|
+
font-size: inherit;
|
|
319
|
+
font-family: inherit;
|
|
320
|
+
background: #1e293b;
|
|
321
|
+
color: #f1f5f9;
|
|
322
|
+
outline: none;
|
|
323
|
+
`
|
|
324
|
+
|
|
325
|
+
// title 내용을 input으로 교체
|
|
326
|
+
titleSpan.textContent = ''
|
|
327
|
+
titleSpan.appendChild(input)
|
|
328
|
+
input.focus()
|
|
329
|
+
input.select()
|
|
330
|
+
|
|
331
|
+
// 저장 함수
|
|
332
|
+
const saveTitle = () => {
|
|
333
|
+
const newTitle = input.value.trim()
|
|
334
|
+
if (newTitle && newTitle !== currentTitle) {
|
|
335
|
+
// taskbar title만 업데이트 (모달 title은 유지)
|
|
336
|
+
modalStore.updateTaskbarTitle(rootModalId, newTitle)
|
|
337
|
+
}
|
|
338
|
+
// input을 text로 다시 변경
|
|
339
|
+
titleSpan.textContent = newTitle || currentTitle
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// blur 시 저장
|
|
343
|
+
input.addEventListener('blur', saveTitle, { once: true })
|
|
344
|
+
|
|
345
|
+
// Enter 시 저장, Escape 시 취소
|
|
346
|
+
input.addEventListener('keydown', (ke) => {
|
|
347
|
+
if (ke.key === 'Enter') {
|
|
348
|
+
ke.preventDefault()
|
|
349
|
+
input.blur()
|
|
350
|
+
} else if (ke.key === 'Escape') {
|
|
351
|
+
ke.preventDefault()
|
|
352
|
+
// blur 이벤트 제거하고 원래 title로 복원
|
|
353
|
+
input.removeEventListener('blur', saveTitle)
|
|
354
|
+
titleSpan.textContent = currentTitle
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
273
359
|
escapeHtml(text) {
|
|
274
360
|
const div = document.createElement('div')
|
|
275
361
|
div.textContent = text
|
|
@@ -141,6 +141,19 @@ class ModalStore {
|
|
|
141
141
|
this.notify();
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Update taskbar-only title for a modal group
|
|
146
|
+
* This does NOT change the actual modal title, only the taskbar display
|
|
147
|
+
* @param {string} rootModalId - Root modal ID of the group
|
|
148
|
+
* @param {string} newTitle - New title to display in taskbar
|
|
149
|
+
*/
|
|
150
|
+
updateTaskbarTitle(rootModalId, newTitle) {
|
|
151
|
+
if (!this.activeModals[rootModalId]) return;
|
|
152
|
+
|
|
153
|
+
this.activeModals[rootModalId].taskbarTitle = newTitle;
|
|
154
|
+
this.notify();
|
|
155
|
+
}
|
|
156
|
+
|
|
144
157
|
bringToFront(modalId) {
|
|
145
158
|
if (!this.activeModals[modalId]) return;
|
|
146
159
|
if (this.modalOrder[this.modalOrder.length - 1] === modalId) return;
|
|
@@ -626,10 +639,13 @@ class ModalStore {
|
|
|
626
639
|
const leafConfig = this.activeModals[leafModalId];
|
|
627
640
|
const rootConfig = this.activeModals[rootId];
|
|
628
641
|
|
|
642
|
+
// taskbarTitle이 있으면 우선 사용 (모달 title과 별도로 관리)
|
|
643
|
+
const displayTitle = rootConfig?.taskbarTitle || leafConfig?.title || leafModalId;
|
|
644
|
+
|
|
629
645
|
return {
|
|
630
646
|
rootModalId: rootId,
|
|
631
647
|
leafModalId,
|
|
632
|
-
title:
|
|
648
|
+
title: displayTitle,
|
|
633
649
|
groupSize: allModalIds.length,
|
|
634
650
|
zIndex: leafConfig?.zIndex || 0,
|
|
635
651
|
modalIds: allModalIds,
|