rails_modal_manager 1.0.40 → 1.0.42

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0df266d9e80cbe4f6999522dfc7d42dd07614651f4e85fca57cd43bd59bd1f56
4
- data.tar.gz: fd760622af26da416a71bf8928ae5247db821de551111f6b60d54c9f9462b01b
3
+ metadata.gz: ba14e569689588bb910cd5c3f2a8c7e192fa259152a6f189eb77e0e3931f9769
4
+ data.tar.gz: d6cf8d712324c8453db93c8ade54bdcd0fcc3d77e92a9ff614d8e929d1712262
5
5
  SHA512:
6
- metadata.gz: b121a1d40ea4d3f3f368e4f3cf69de017f2afa3fc999c3bc03377a376d0011912d970966e34d6d5310e42d5c65c2dc8aeeb1c5b398bfb7b53da14d5459d48a15
7
- data.tar.gz: 169d8b9067a9ea55f0749324e1a349a634f0741a42c776b30e9410478dc20e6d0f1be3f50d2f0c30a048a4a00a45ee2cc9db92f10e1759de3817294e2b2e2fde
6
+ metadata.gz: '0309e4dd66960b7e7b20944d93c348987fc2a7ab7d291e67856a4d079e259967283ef8b0be502fadf627513109fc79c9eb6bdedf164727c7595636cf8179667c'
7
+ data.tar.gz: 0dbc3f5ef3257b8cd99e518ca6423f6efdb7623a8b11955bfb8ef2531e08134a3a61e43d070d47a205b9a0a8de15bba06dbea0750f92a0f90b473a0a8fef468e
@@ -11,14 +11,31 @@ 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
18
18
  this.handleSidebarToggled = this.handleSidebarToggled.bind(this)
19
+ this.handleModalClosed = this.handleModalClosed.bind(this)
20
+
19
21
  const modal = this.element.closest('.rmm-modal')
20
22
  if (modal) {
21
23
  modal.addEventListener('rmm-sidebar:toggled', this.handleSidebarToggled)
24
+ modal.addEventListener('rmm:closed', this.handleModalClosed)
25
+ }
26
+
27
+ // Title 더블클릭 이벤트 리스너 추가 및 원래 title 저장
28
+ this.setupTitleEdit()
29
+ }
30
+
31
+ setupTitleEdit() {
32
+ const titleEl = this.element.querySelector('.rmm-header-title')
33
+ if (titleEl) {
34
+ // 원래 title 저장 (모달 닫힐 때 복원용)
35
+ this.originalTitle = titleEl.textContent.trim()
36
+
37
+ titleEl.addEventListener('dblclick', (e) => this.editTitle(e))
38
+ titleEl.style.cursor = 'default'
22
39
  }
23
40
  }
24
41
 
@@ -26,6 +43,21 @@ export default class extends Controller {
26
43
  const modal = this.element.closest('.rmm-modal')
27
44
  if (modal) {
28
45
  modal.removeEventListener('rmm-sidebar:toggled', this.handleSidebarToggled)
46
+ modal.removeEventListener('rmm:closed', this.handleModalClosed)
47
+ }
48
+ }
49
+
50
+ // 모달 닫힐 때 title을 원래 값으로 복원
51
+ handleModalClosed() {
52
+ this.resetTitle()
53
+ }
54
+
55
+ resetTitle() {
56
+ if (!this.originalTitle) return
57
+
58
+ const titleEl = this.element.querySelector('.rmm-header-title')
59
+ if (titleEl) {
60
+ titleEl.textContent = this.originalTitle
29
61
  }
30
62
  }
31
63
 
@@ -173,4 +205,92 @@ export default class extends Controller {
173
205
  if (hiddenIcon) hiddenIcon.style.display = 'none'
174
206
  }
175
207
  }
208
+
209
+ // ============================================
210
+ // Title Edit (Double-click to edit)
211
+ // ============================================
212
+
213
+ editTitle(e) {
214
+ e.stopPropagation()
215
+ e.preventDefault()
216
+
217
+ const titleEl = e.currentTarget
218
+ if (!titleEl) return
219
+
220
+ // 이미 수정 중인 경우 무시
221
+ if (titleEl.querySelector('input')) return
222
+
223
+ const currentTitle = titleEl.textContent.trim()
224
+
225
+ // input 생성
226
+ const input = document.createElement('input')
227
+ input.type = 'text'
228
+ input.value = currentTitle
229
+ input.className = 'rmm-header-title-input'
230
+ input.style.cssText = `
231
+ width: 100%;
232
+ min-width: 100px;
233
+ max-width: 300px;
234
+ padding: 2px 6px;
235
+ border: 1px solid rgba(255,255,255,0.5);
236
+ border-radius: 4px;
237
+ font-size: inherit;
238
+ font-weight: inherit;
239
+ font-family: inherit;
240
+ background: rgba(0,0,0,0.2);
241
+ color: inherit;
242
+ outline: none;
243
+ `
244
+
245
+ // title 내용을 input으로 교체
246
+ titleEl.textContent = ''
247
+ titleEl.appendChild(input)
248
+ input.focus()
249
+ input.select()
250
+
251
+ // 저장 함수
252
+ const saveTitle = () => {
253
+ const newTitle = input.value.trim()
254
+ const finalTitle = newTitle || currentTitle
255
+
256
+ // title 업데이트
257
+ titleEl.textContent = finalTitle
258
+
259
+ // modalStore의 title도 업데이트
260
+ if (newTitle && newTitle !== currentTitle) {
261
+ this.updateModalTitle(finalTitle)
262
+ }
263
+ }
264
+
265
+ // blur 시 저장
266
+ input.addEventListener('blur', saveTitle, { once: true })
267
+
268
+ // Enter 시 저장, Escape 시 취소
269
+ input.addEventListener('keydown', (ke) => {
270
+ if (ke.key === 'Enter') {
271
+ ke.preventDefault()
272
+ input.blur()
273
+ } else if (ke.key === 'Escape') {
274
+ ke.preventDefault()
275
+ input.removeEventListener('blur', saveTitle)
276
+ titleEl.textContent = currentTitle
277
+ }
278
+ })
279
+ }
280
+
281
+ updateModalTitle(newTitle) {
282
+ const modal = this.getModalElement()
283
+ if (!modal) return
284
+
285
+ const modalId = modal.id || this.modalIdValue
286
+ if (!modalId) return
287
+
288
+ // modalStore에서 title 업데이트
289
+ import("rails_modal_manager/modal_store").then(module => {
290
+ const modalStore = module.default
291
+ if (modalStore && modalStore.updateModal) {
292
+ modalStore.updateModal(modalId, { title: newTitle })
293
+ }
294
+ })
295
+ }
176
296
  }
@@ -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: leafConfig?.title || leafModalId,
648
+ title: displayTitle,
633
649
  groupSize: allModalIds.length,
634
650
  zIndex: leafConfig?.zIndex || 0,
635
651
  modalIds: allModalIds,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsModalManager
4
- VERSION = "1.0.40"
4
+ VERSION = "1.0.42"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_modal_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.40
4
+ version: 1.0.42
5
5
  platform: ruby
6
6
  authors:
7
7
  - reshacs