rails_modal_manager 1.0.55 → 1.0.57

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: e0cf503bb2cd8309b4682f5fa03fbac83853d251624a0e695110dd7ca984b1e5
4
- data.tar.gz: eb75b69501205a841d2fa1220f2510bea5e96ffb169f19307dedb968efc6edbb
3
+ metadata.gz: 91f46719ac2c4bf2aa6b8b33d883b85ee97f8dbc3490c7162441855ff6701e42
4
+ data.tar.gz: 881b2327c4fa56fa3fa4199655d26b3bb360b8e79ebdab4efec31db5d3368188
5
5
  SHA512:
6
- metadata.gz: 9f09c9cfdecc894a959360eb1f4ee4f8a36841b1d19e9e2922803e2d017a4ff810de075ac7a7e8c3ce9fe7d0ff6c8059f84a9252a2c80e0435278eed42f33153
7
- data.tar.gz: 595f2da63377cde83786907e7c8adb78620ba965ad8cae92e605b072492291bc2ca7ce1f8a2cc3181254eb3c765db1f0457a840588fb22591e1ce96f522aba31
6
+ metadata.gz: 97bfd7a727035f23b11cc926911f1fb0465a9df3b48e0cec2cf73b69c06983d9b0fbf329732c10eec541ade3c5943f209cd2e30ddb79dd3aa38816a8ea6be503
7
+ data.tar.gz: 2f7b805a605d217f0af8a4ef98745b3f2204baed44ee6aa15ab839c703afcd025b73363b6dfe83072b09d06cc93f3396e6298f87f50750b6d7bfbde45e3c940e
@@ -30,7 +30,9 @@
30
30
 
31
31
  /* Modal */
32
32
  --rmm-modal-bg: #ffffff;
33
- --rmm-modal-border: rgba(0, 0, 0, 0.1);
33
+ /* v1.0.57 — 모달 외곽 하드 border 제거 (drop shadow 경계 표현).
34
+ 기존처럼 선이 필요하면 :root { --rmm-modal-border: rgba(0,0,0,0.1); } 로 복구 */
35
+ --rmm-modal-border: transparent;
34
36
  --rmm-modal-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
35
37
  --rmm-modal-radius: 12px;
36
38
 
@@ -38,47 +40,56 @@
38
40
  --rmm-overlay-bg: rgba(0, 0, 0, 0.5);
39
41
  --rmm-overlay-blur: 0px;
40
42
 
41
- /* Header */
42
- --rmm-header-bg: #f8fafc;
43
- --rmm-header-border: rgba(0, 0, 0, 0.1);
44
- --rmm-header-text: #1e293b;
45
- --rmm-header-height: 56px;
43
+ /* Header — v1.0.57 블루 차콜 (사용자 지정 팔레트) */
44
+ --rmm-header-bg: #3e6187;
45
+ --rmm-header-border: rgba(0, 0, 0, 0.12);
46
+ --rmm-header-text: #ffffff;
47
+ --rmm-header-height: 52px;
48
+ --rmm-header-title-align: center; /* left | center — 필요 시 개별 모달에서 override */
49
+ --rmm-header-accent: #60a5fa;
46
50
 
47
- /* Header - Depth Colors (Parent/Child/Grandchild) */
48
- --rmm-header-bg-depth-0: #3b82f6;
51
+ /* Header - Depth Colors (자식·손자 모달은 살짝 밝아지는 계단) */
52
+ --rmm-header-bg-depth-0: #3e6187;
49
53
  --rmm-header-text-depth-0: #ffffff;
50
- --rmm-header-bg-depth-1: #0ea5e9;
54
+ --rmm-header-bg-depth-1: #4b7099;
51
55
  --rmm-header-text-depth-1: #ffffff;
52
- --rmm-header-bg-depth-2: #06b6d4;
56
+ --rmm-header-bg-depth-2: #587fab;
53
57
  --rmm-header-text-depth-2: #ffffff;
54
- --rmm-header-bg-depth-3: #64748B;
58
+ --rmm-header-bg-depth-3: #6590bd;
55
59
  --rmm-header-text-depth-3: #ffffff;
56
60
 
57
- /* Footer */
61
+ /* Footer — v1.0.57 정제: 본문과 분리감 + 살짝 톤다운된 신뢰감 있는 베이스 */
58
62
  --rmm-footer-bg: #f8fafc;
59
- --rmm-footer-border: rgba(0, 0, 0, 0.1);
60
- --rmm-footer-text: #334155;
61
- --rmm-footer-height: 64px;
62
-
63
- /* Sidebar */
64
- --rmm-sidebar-bg: #f1f5f9;
65
- --rmm-sidebar-border: rgba(0, 0, 0, 0.1);
63
+ --rmm-footer-border: rgba(15, 23, 42, 0.08);
64
+ --rmm-footer-text: #475569;
65
+ --rmm-footer-height: 60px;
66
+ --rmm-footer-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
67
+
68
+ /* Sidebar — v1.0.57 다크 블루 톤 통합 (헤더/서브메뉴와 좌측 한 판으로 연결)
69
+ 사용자 지정 팔레트(헤더 #3e6187 · 서브메뉴 #385678 · 활성 #406389)와 일관성 유지 */
70
+ --rmm-sidebar-bg: #385678;
71
+ --rmm-sidebar-border: rgba(0, 0, 0, 0.15);
66
72
  --rmm-sidebar-width: 240px;
67
73
  --rmm-sidebar-collapsed-width: 64px;
68
- --rmm-sidebar-text: #334155;
69
- --rmm-sidebar-icon: #64748b;
70
- --rmm-sidebar-item-hover: rgba(0, 0, 0, 0.05);
71
- --rmm-sidebar-item-active-bg: rgba(59, 130, 246, 0.1);
72
- --rmm-sidebar-item-active-text: #3b82f6;
73
-
74
- /* Submenu */
75
- --rmm-submenu-bg: #f1f5f9;
76
- --rmm-submenu-container-bg: #ffffff;
77
- --rmm-submenu-height: 56px;
78
- --rmm-submenu-item-text: #64748b;
79
- --rmm-submenu-item-hover-bg: rgba(0, 0, 0, 0.04);
80
- --rmm-submenu-item-active-bg: #3b82f6;
74
+ --rmm-sidebar-text: #acc0d8;
75
+ --rmm-sidebar-icon: #acc0d8;
76
+ --rmm-sidebar-item-hover: rgba(255, 255, 255, 0.08);
77
+ --rmm-sidebar-item-active-bg: #406389;
78
+ --rmm-sidebar-item-active-text: #ffffff;
79
+ --rmm-sidebar-item-active-icon: #ffffff;
80
+
81
+ /* Submenu — v1.0.57 블루 차콜 (사용자 지정 팔레트)
82
+ 컨테이너 = 미선택 탭 = #385678 로 동일 (미선택 탭은 배경에 녹아듦)
83
+ 선택 탭은 살짝 밝은 #406389 로 은은히 도드라짐 */
84
+ --rmm-submenu-bg: #385678;
85
+ --rmm-submenu-container-bg: #385678;
86
+ --rmm-submenu-height: 42px;
87
+ --rmm-submenu-item-text: #acc0d8;
88
+ --rmm-submenu-item-hover-bg: rgba(255, 255, 255, 0.06);
89
+ --rmm-submenu-item-active-bg: #406389;
81
90
  --rmm-submenu-item-active-text: #ffffff;
91
+ --rmm-submenu-item-active-border: transparent;
92
+ --rmm-submenu-item-active-underline: #ffffff;
82
93
 
83
94
  /* Content */
84
95
  --rmm-content-bg: #ffffff;
@@ -122,9 +133,11 @@
122
133
  --rmm-animation-timing: cubic-bezier(0.4, 0, 0.2, 1);
123
134
 
124
135
  /* Z-index */
136
+ /* v1.0.57 — z-index 계층 재정렬: overlay < taskbar < modal
137
+ → 모달이 최소화 태스크바 위에 항상 노출, 태스크바는 여전히 overlay 위라 클릭 가능 */
125
138
  --rmm-z-overlay: 2000;
126
- --rmm-z-modal: 2001;
127
139
  --rmm-z-taskbar: 2100;
140
+ --rmm-z-modal: 2200;
128
141
  }
129
142
 
130
143
  /* ============================================
@@ -132,42 +145,49 @@
132
145
  ============================================ */
133
146
  .dark {
134
147
  --rmm-modal-bg: #1e293b;
135
- --rmm-modal-border: rgba(255, 255, 255, 0.1);
148
+ --rmm-modal-border: transparent;
136
149
  --rmm-modal-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
137
150
 
138
151
  --rmm-overlay-bg: rgba(0, 0, 0, 0.7);
139
152
 
140
- --rmm-header-bg: #0f172a;
141
- --rmm-header-border: rgba(255, 255, 255, 0.1);
153
+ /* v1.0.57 — 라이트와 동일 원칙 유지, 한 단계 더 어두운 톤 */
154
+ --rmm-header-bg: #0b1220;
155
+ --rmm-header-border: rgba(255, 255, 255, 0.08);
142
156
  --rmm-header-text: #f1f5f9;
143
-
144
- /* Header - Depth Colors (Dark Theme) */
145
- --rmm-header-bg-depth-0: linear-gradient(135deg, #4c51bf 0%, #553c9a 100%);
146
- --rmm-header-text-depth-0: #ffffff;
147
- --rmm-header-bg-depth-1: linear-gradient(135deg, #0d7377 0%, #14a085 100%);
148
- --rmm-header-text-depth-1: #ffffff;
149
- --rmm-header-bg-depth-2: linear-gradient(135deg, #c0392b 0%, #d68910 100%);
150
- --rmm-header-text-depth-2: #ffffff;
151
- --rmm-header-bg-depth-3: linear-gradient(135deg, #a0185e 0%, #c0392b 100%);
152
- --rmm-header-text-depth-3: #ffffff;
157
+ --rmm-header-accent: #60a5fa;
158
+
159
+ /* Header - Depth Colors (Dark Theme) 단조 차콜 계단 */
160
+ --rmm-header-bg-depth-0: #0b1220;
161
+ --rmm-header-text-depth-0: #f8fafc;
162
+ --rmm-header-bg-depth-1: #111a2e;
163
+ --rmm-header-text-depth-1: #f8fafc;
164
+ --rmm-header-bg-depth-2: #16223c;
165
+ --rmm-header-text-depth-2: #f8fafc;
166
+ --rmm-header-bg-depth-3: #1c2a4a;
167
+ --rmm-header-text-depth-3: #f8fafc;
153
168
 
154
169
  --rmm-footer-bg: #0f172a;
155
- --rmm-footer-border: rgba(255, 255, 255, 0.1);
156
- --rmm-footer-text: #e2e8f0;
170
+ --rmm-footer-border: rgba(255, 255, 255, 0.08);
171
+ --rmm-footer-text: #cbd5e1;
172
+ --rmm-footer-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
157
173
 
158
- --rmm-sidebar-bg: #0f172a;
159
- --rmm-sidebar-border: rgba(255, 255, 255, 0.1);
160
- --rmm-sidebar-text: #e2e8f0;
174
+ --rmm-sidebar-bg: #111a2e;
175
+ --rmm-sidebar-border: rgba(255, 255, 255, 0.08);
176
+ --rmm-sidebar-text: #cbd5e1;
161
177
  --rmm-sidebar-icon: #94a3b8;
162
178
  --rmm-sidebar-item-hover: rgba(255, 255, 255, 0.05);
163
- --rmm-sidebar-item-active-bg: rgba(59, 130, 246, 0.2);
179
+ --rmm-sidebar-item-active-bg: #1e293b;
180
+ --rmm-sidebar-item-active-text: #f8fafc;
181
+ --rmm-sidebar-item-active-icon: #f8fafc;
164
182
 
165
- --rmm-submenu-bg: #1e293b;
166
- --rmm-submenu-container-bg: #0f172a;
183
+ --rmm-submenu-bg: #0b1220;
184
+ --rmm-submenu-container-bg: #0b1220;
167
185
  --rmm-submenu-item-text: #94a3b8;
168
- --rmm-submenu-item-hover-bg: rgba(255, 255, 255, 0.08);
169
- --rmm-submenu-item-active-bg: #3b82f6;
170
- --rmm-submenu-item-active-text: #ffffff;
186
+ --rmm-submenu-item-hover-bg: rgba(255, 255, 255, 0.06);
187
+ --rmm-submenu-item-active-bg: #1e293b;
188
+ --rmm-submenu-item-active-text: #f8fafc;
189
+ --rmm-submenu-item-active-border: transparent;
190
+ --rmm-submenu-item-active-underline: #f8fafc;
171
191
 
172
192
  --rmm-content-bg: #1e293b;
173
193
  --rmm-content-text: #f1f5f9;
@@ -230,9 +250,13 @@
230
250
  display: flex;
231
251
  flex-direction: column;
232
252
  background: var(--rmm-modal-bg);
233
- border: 1px solid var(--rmm-modal-border);
253
+ /* v1.0.57 — border 외곽 라인(rmm-modal-outline)로 분리 관리.
254
+ 라인 없을 땐 width 0 으로 계산까지 빼 subpixel 경계 아티팩트 방지. */
255
+ border: var(--rmm-modal-border-width, 0) solid var(--rmm-modal-border, transparent);
234
256
  border-radius: var(--rmm-modal-radius);
235
257
  box-shadow: var(--rmm-modal-shadow);
258
+ /* v1.0.57 — 헤더 아래 그림자 오버레이용 stacking context 확보 (::before 가 z-index:1 로 올라감) */
259
+ isolation: isolate;
236
260
  z-index: var(--rmm-z-modal);
237
261
  opacity: 0;
238
262
  visibility: hidden;
@@ -251,6 +275,22 @@
251
275
  transform: scale(1) translateY(0);
252
276
  }
253
277
 
278
+ /* v1.0.57 — 헤더 바로 아래 그림자 오버레이
279
+ 서브메뉴/사이드바/body 유무와 무관하게 헤더 아래 항상 은은한 drop shadow 효과 제공.
280
+ .rmm-modal::before 는 positioned descendant 로서 non-positioned 자식(header/submenu/body) 위에 렌더됨.
281
+ z-index 를 header 자체에 주지 않아 rounded-corner subpixel 아티팩트 없음. */
282
+ .rmm-modal::before {
283
+ content: "";
284
+ position: absolute;
285
+ top: var(--rmm-header-height);
286
+ left: 0;
287
+ right: 0;
288
+ height: 6px;
289
+ background: var(--rmm-header-underlay-shadow, linear-gradient(to bottom, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0) 100%));
290
+ pointer-events: none;
291
+ z-index: 1;
292
+ }
293
+
254
294
  /* Minimized state with fade animation - animates to bottom-left */
255
295
  .rmm-modal.rmm-minimized {
256
296
  opacity: 0 !important;
@@ -360,6 +400,7 @@
360
400
  Modal Header
361
401
  ============================================ */
362
402
  .rmm-header {
403
+ position: relative;
363
404
  display: flex;
364
405
  align-items: center;
365
406
  justify-content: space-between;
@@ -367,36 +408,110 @@
367
408
  height: var(--rmm-header-height);
368
409
  min-height: var(--rmm-header-height);
369
410
  background: var(--rmm-header-bg);
370
- border-bottom: 1px solid var(--rmm-header-border);
411
+ /* v1.0.57 헤더 상/하 모서리 모달 radius 와 정확히 맞춰 subpixel 하이라이트 방지 */
412
+ border-top-left-radius: inherit;
413
+ border-top-right-radius: inherit;
414
+ /* v1.0.57 — 헤더와 서브메뉴 사이 하드 라인 제거. 미묘한 채도 차이만으로 레이어 분리 표현 */
415
+ border-bottom: 1px solid var(--rmm-header-divider, transparent);
416
+ /* v1.0.57 — 헤더 하단 은은한 drop shadow (1.png 레퍼런스) — 깊이감 표현 */
417
+ box-shadow: var(--rmm-header-shadow, 0 3px 6px rgba(0, 0, 0, 0.12));
371
418
  user-select: none;
372
419
  flex-shrink: 0;
373
420
  }
374
421
 
422
+ /* v1.0.57 — 드래그는 .rmm-drag-handle 에서만 시작되므로
423
+ 헤더 전체에 move 커서를 주지 않는다. 커서 힌트는 드래그핸들에만. */
375
424
  .rmm-header.rmm-draggable {
376
- cursor: move;
425
+ cursor: default;
377
426
  }
378
427
 
379
428
  .rmm-header-left {
380
429
  display: flex;
381
430
  align-items: center;
382
- gap: 12px;
383
- flex: 1;
431
+ gap: 10px;
432
+ flex: 0 0 auto;
384
433
  min-width: 0;
434
+ z-index: 1;
385
435
  }
386
436
 
437
+ /* v1.0.57 — 타이틀 중앙 정렬 (세로·가로 모두 flex 로 확실하게)
438
+ h2 기본 margin 을 0 으로 리셋하고 inset: 0 + flex 로 absolute 영역 내 중앙 정렬.
439
+ 하위 구조:
440
+ .rmm-header-title-text — 제목 텍스트
441
+ .rmm-header-section — 모바일 + 사이드바 있는 경우 현재 메뉴명(서브타이틀) */
387
442
  .rmm-header-title {
388
- font-size: 16px;
443
+ position: absolute;
444
+ inset: 0;
445
+ margin: 0;
446
+ display: flex;
447
+ align-items: center;
448
+ justify-content: var(--rmm-header-title-justify, center);
449
+ gap: 6px;
450
+ font-size: 15px;
389
451
  font-weight: 600;
390
452
  color: var(--rmm-header-text);
453
+ line-height: 1;
454
+ padding: 0 88px; /* 좌우 컨트롤 영역 침범 방지 */
455
+ pointer-events: none;
456
+ letter-spacing: -0.01em;
457
+ min-width: 0;
458
+ }
459
+ .rmm-header-title-text {
460
+ white-space: nowrap;
461
+ overflow: hidden;
462
+ text-overflow: ellipsis;
463
+ min-width: 0;
464
+ }
465
+ .rmm-header-title > *:not(.rmm-header-title-text):not(.rmm-header-section) {
466
+ white-space: nowrap;
467
+ overflow: hidden;
468
+ text-overflow: ellipsis;
469
+ max-width: 100%;
470
+ }
471
+
472
+ /* 사이드바 메뉴명 서브타이틀 — 사이드바 있는 모달에서 현재 선택 메뉴명 노출 (v1.0.57) */
473
+ .rmm-header-section {
474
+ display: none;
475
+ font-size: 13px;
476
+ font-weight: 500;
477
+ color: var(--rmm-header-text);
478
+ opacity: 0.75;
391
479
  white-space: nowrap;
392
480
  overflow: hidden;
393
481
  text-overflow: ellipsis;
482
+ flex-shrink: 1;
483
+ min-width: 0;
484
+ letter-spacing: -0.01em;
485
+ }
486
+ .rmm-header-section:not(:empty)::before {
487
+ content: "›";
488
+ margin-right: 6px;
489
+ opacity: 0.55;
490
+ font-weight: 400;
491
+ }
492
+ /* 사이드바 있는 모달의 헤더 섹션에 텍스트가 있을 때 노출 (데스크톱 + 모바일 공통) */
493
+ .rmm-modal:has(.rmm-sidebar) .rmm-header-section:not(:empty) {
494
+ display: inline-flex;
495
+ align-items: center;
496
+ }
497
+
498
+ /* 좌측 정렬이 필요한 경우: 기존 동작 복원 */
499
+ .rmm-header.rmm-header-title-left .rmm-header-title {
500
+ position: static;
501
+ transform: none;
502
+ text-align: left;
503
+ padding: 0;
504
+ pointer-events: auto;
505
+ }
506
+ .rmm-header.rmm-header-title-left .rmm-header-left {
507
+ flex: 1;
394
508
  }
395
509
 
396
510
  .rmm-header-controls {
397
511
  display: flex;
398
512
  align-items: center;
399
513
  gap: 4px;
514
+ z-index: 1; /* 중앙 absolute 타이틀 위에 올라가도록 */
400
515
  }
401
516
 
402
517
  .rmm-header-btn {
@@ -416,7 +531,8 @@
416
531
 
417
532
  .rmm-header-btn:hover {
418
533
  opacity: 1;
419
- background: var(--rmm-btn-ghost-hover);
534
+ /* 헤더 배경이 어두운 톤이므로 반투명 흰색 오버레이 사용 */
535
+ background: rgba(255, 255, 255, 0.12);
420
536
  }
421
537
 
422
538
  .rmm-header-btn.rmm-close-btn {
@@ -530,7 +646,8 @@
530
646
  /* Depth 0 - Parent Modal (Purple gradient) */
531
647
  .rmm-modal.rmm-depth-0 .rmm-header {
532
648
  background: var(--rmm-header-bg-depth-0);
533
- border-bottom-color: rgba(255, 255, 255, 0.2);
649
+ /* v1.0.57 — 헤더-서브메뉴 경계 하이라이트 라인 제거 (하드 라인 대신 색차로만 분리) */
650
+ border-bottom-color: var(--rmm-header-divider, transparent);
534
651
  }
535
652
 
536
653
  .rmm-modal.rmm-depth-0 .rmm-header-title {
@@ -557,7 +674,8 @@
557
674
  /* Depth 1 - Child Modal (Green gradient) */
558
675
  .rmm-modal.rmm-depth-1 .rmm-header {
559
676
  background: var(--rmm-header-bg-depth-1);
560
- border-bottom-color: rgba(255, 255, 255, 0.2);
677
+ /* v1.0.57 — 헤더-서브메뉴 경계 하이라이트 라인 제거 (하드 라인 대신 색차로만 분리) */
678
+ border-bottom-color: var(--rmm-header-divider, transparent);
561
679
  }
562
680
 
563
681
  .rmm-modal.rmm-depth-1 .rmm-header-title {
@@ -584,7 +702,8 @@
584
702
  /* Depth 2 - Grandchild Modal (Orange gradient) */
585
703
  .rmm-modal.rmm-depth-2 .rmm-header {
586
704
  background: var(--rmm-header-bg-depth-2);
587
- border-bottom-color: rgba(255, 255, 255, 0.2);
705
+ /* v1.0.57 — 헤더-서브메뉴 경계 하이라이트 라인 제거 (하드 라인 대신 색차로만 분리) */
706
+ border-bottom-color: var(--rmm-header-divider, transparent);
588
707
  }
589
708
 
590
709
  .rmm-modal.rmm-depth-2 .rmm-header-title {
@@ -613,7 +732,8 @@
613
732
  .rmm-modal.rmm-depth-4 .rmm-header,
614
733
  .rmm-modal.rmm-depth-5 .rmm-header {
615
734
  background: var(--rmm-header-bg-depth-3);
616
- border-bottom-color: rgba(255, 255, 255, 0.2);
735
+ /* v1.0.57 — 헤더-서브메뉴 경계 하이라이트 라인 제거 (하드 라인 대신 색차로만 분리) */
736
+ border-bottom-color: var(--rmm-header-divider, transparent);
617
737
  }
618
738
 
619
739
  .rmm-modal.rmm-depth-3 .rmm-header-title,
@@ -764,7 +884,7 @@
764
884
  }
765
885
 
766
886
  /* ============================================
767
- Modal Sidebar
887
+ Modal Sidebar (v1.0.57 다크 블루 톤 — 헤더/서브메뉴와 통합)
768
888
  ============================================ */
769
889
  .rmm-sidebar {
770
890
  width: var(--rmm-sidebar-width);
@@ -785,20 +905,39 @@
785
905
 
786
906
  .rmm-sidebar-nav {
787
907
  flex: 1;
788
- padding: 8px;
908
+ padding: 10px 8px;
789
909
  overflow-y: auto;
910
+ display: flex;
911
+ flex-direction: column;
912
+ gap: 2px;
913
+ }
914
+
915
+ /* 다크 사이드바 스크롤바 톤 다운 */
916
+ .rmm-sidebar-nav::-webkit-scrollbar {
917
+ width: 4px;
918
+ }
919
+ .rmm-sidebar-nav::-webkit-scrollbar-track {
920
+ background: transparent;
921
+ }
922
+ .rmm-sidebar-nav::-webkit-scrollbar-thumb {
923
+ background: rgba(255, 255, 255, 0.15);
924
+ border-radius: 2px;
925
+ }
926
+ .rmm-sidebar-nav::-webkit-scrollbar-thumb:hover {
927
+ background: rgba(255, 255, 255, 0.25);
790
928
  }
791
929
 
792
930
  .rmm-sidebar-item {
931
+ position: relative;
793
932
  display: flex;
794
933
  align-items: center;
795
934
  gap: 12px;
796
- padding: 10px 12px;
797
- border-radius: 8px;
935
+ padding: 9px 12px;
936
+ border-radius: 6px;
798
937
  cursor: pointer;
799
938
  color: var(--rmm-sidebar-text);
800
939
  text-decoration: none;
801
- transition: background-color 0.15s;
940
+ transition: background-color 0.15s ease, color 0.15s ease;
802
941
  white-space: nowrap;
803
942
  /* Button reset for when using <button> */
804
943
  border: none;
@@ -809,13 +948,27 @@
809
948
  font-size: inherit;
810
949
  }
811
950
 
812
- .rmm-sidebar-item:hover {
951
+ .rmm-sidebar-item:hover:not(.rmm-active) {
813
952
  background: var(--rmm-sidebar-item-hover);
953
+ color: #ffffff;
814
954
  }
815
955
 
816
956
  .rmm-sidebar-item.rmm-active {
817
957
  background: var(--rmm-sidebar-item-active-bg);
818
958
  color: var(--rmm-sidebar-item-active-text);
959
+ font-weight: 600;
960
+ }
961
+
962
+ /* 활성 항목 좌측 3px 포인트 바 — 서브메뉴 활성 탭 하단 바와 대칭 */
963
+ .rmm-sidebar-item.rmm-active::before {
964
+ content: "";
965
+ position: absolute;
966
+ left: 0;
967
+ top: 8px;
968
+ bottom: 8px;
969
+ width: 3px;
970
+ background: var(--rmm-sidebar-item-active-underline, #ffffff);
971
+ border-radius: 0 2px 2px 0;
819
972
  }
820
973
 
821
974
  .rmm-sidebar-item-icon {
@@ -826,18 +979,24 @@
826
979
  height: 20px;
827
980
  flex-shrink: 0;
828
981
  color: var(--rmm-sidebar-icon);
982
+ transition: color 0.15s ease;
983
+ }
984
+
985
+ .rmm-sidebar-item:hover:not(.rmm-active) .rmm-sidebar-item-icon {
986
+ color: #ffffff;
829
987
  }
830
988
 
831
989
  .rmm-sidebar-item.rmm-active .rmm-sidebar-item-icon {
832
- color: var(--rmm-sidebar-item-active-text);
990
+ color: var(--rmm-sidebar-item-active-icon, var(--rmm-sidebar-item-active-text));
833
991
  }
834
992
 
835
993
  .rmm-sidebar-item-label {
836
- font-size: 14px;
994
+ font-size: 13px;
837
995
  font-weight: 500;
838
996
  flex: 1;
839
997
  overflow: hidden;
840
998
  text-overflow: ellipsis;
999
+ letter-spacing: -0.01em;
841
1000
  }
842
1001
 
843
1002
  .rmm-sidebar-collapsed .rmm-sidebar-item {
@@ -946,27 +1105,34 @@
946
1105
  }
947
1106
 
948
1107
  /* ============================================
949
- Modal Submenu (Pill/Tab Style)
1108
+ Modal Submenu (v1.0.57 Dark Tab Style)
1109
+ ---------------------------------------------
1110
+ 레퍼런스 1.png: 다크 배경 + 흰색 활성 탭 + 본문과 매끄럽게 이어짐.
1111
+ 비활성 탭은 어두운 배경 위 연한 회색 글자.
1112
+ pill 호환은 .rmm-submenu-pill 클래스로 유지.
950
1113
  ============================================ */
951
1114
  .rmm-submenu {
952
1115
  display: flex;
953
- align-items: center;
1116
+ align-items: flex-end;
954
1117
  justify-content: flex-start;
955
- gap: 6px;
956
- padding: 8px 12px;
1118
+ gap: 2px;
1119
+ padding: 6px 10px 0;
957
1120
  min-height: var(--rmm-submenu-height);
958
1121
  background: var(--rmm-submenu-container-bg);
959
1122
  overflow-x: auto;
960
1123
  flex-shrink: 0;
961
1124
  }
962
1125
 
1126
+ /* 내부 컨테이너는 pill 스타일과의 호환을 위해 유지하지만 시각적으로 투명 */
963
1127
  .rmm-submenu-inner {
964
1128
  display: flex;
965
- align-items: center;
966
- gap: 4px;
967
- padding: 4px;
968
- background: var(--rmm-submenu-bg);
969
- border-radius: 8px;
1129
+ align-items: flex-end;
1130
+ gap: 2px;
1131
+ padding: 0;
1132
+ background: transparent;
1133
+ border-radius: 0;
1134
+ flex: 1;
1135
+ min-width: 0;
970
1136
  }
971
1137
 
972
1138
  .rmm-submenu::-webkit-scrollbar {
@@ -978,36 +1144,73 @@
978
1144
  }
979
1145
 
980
1146
  .rmm-submenu::-webkit-scrollbar-thumb {
981
- background: var(--rmm-modal-border);
1147
+ background: rgba(255, 255, 255, 0.2);
982
1148
  border-radius: 2px;
983
1149
  }
984
1150
 
985
1151
  .rmm-submenu-item {
1152
+ position: relative;
986
1153
  display: flex;
987
1154
  align-items: center;
988
1155
  justify-content: center;
989
1156
  gap: 6px;
990
- padding: 8px 16px;
991
- font-size: 14px;
1157
+ padding: 9px 18px;
1158
+ font-size: 13px;
992
1159
  font-weight: 500;
993
1160
  color: var(--rmm-submenu-item-text);
994
1161
  background: transparent;
995
1162
  border: none;
996
- border-radius: 6px;
1163
+ border-radius: 6px 6px 0 0;
997
1164
  cursor: pointer;
998
1165
  white-space: nowrap;
999
- transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
1166
+ transition: background-color 0.15s ease, color 0.15s ease;
1000
1167
  }
1001
1168
 
1002
1169
  .rmm-submenu-item:hover:not(.rmm-active):not(.rmm-disabled) {
1003
1170
  background: var(--rmm-submenu-item-hover-bg);
1004
- color: var(--rmm-content-text);
1171
+ color: #f8fafc;
1005
1172
  }
1006
1173
 
1007
1174
  .rmm-submenu-item.rmm-active {
1008
1175
  background: var(--rmm-submenu-item-active-bg);
1009
1176
  color: var(--rmm-submenu-item-active-text);
1010
- box-shadow: 0 1px 3px rgba(59, 130, 246, 0.3);
1177
+ font-weight: 600;
1178
+ }
1179
+
1180
+ /* 활성 탭 하단: 흰색 포인트 바 (1.png 레퍼런스) */
1181
+ .rmm-submenu-item.rmm-active::after {
1182
+ content: "";
1183
+ position: absolute;
1184
+ left: 0;
1185
+ right: 0;
1186
+ bottom: 0;
1187
+ height: 3px;
1188
+ background: var(--rmm-submenu-item-active-underline, #ffffff);
1189
+ }
1190
+
1191
+ /* Legacy pill 호환 모드 (원하는 경우 .rmm-submenu.rmm-submenu-pill 으로 사용) */
1192
+ .rmm-submenu.rmm-submenu-pill {
1193
+ align-items: center;
1194
+ padding: 8px 12px;
1195
+ border-bottom: none;
1196
+ gap: 6px;
1197
+ }
1198
+ .rmm-submenu.rmm-submenu-pill .rmm-submenu-inner {
1199
+ padding: 4px;
1200
+ background: var(--rmm-submenu-bg);
1201
+ border-radius: 8px;
1202
+ flex: 0 0 auto;
1203
+ }
1204
+ .rmm-submenu.rmm-submenu-pill .rmm-submenu-item {
1205
+ padding: 8px 16px;
1206
+ border-radius: 6px;
1207
+ }
1208
+ .rmm-submenu.rmm-submenu-pill .rmm-submenu-item.rmm-active {
1209
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
1210
+ }
1211
+ .rmm-submenu.rmm-submenu-pill .rmm-submenu-item.rmm-active::before,
1212
+ .rmm-submenu.rmm-submenu-pill .rmm-submenu-item.rmm-active::after {
1213
+ display: none;
1011
1214
  }
1012
1215
 
1013
1216
  .rmm-submenu-item.rmm-disabled {
@@ -1108,28 +1311,37 @@
1108
1311
  }
1109
1312
 
1110
1313
  /* ============================================
1111
- Modal Footer
1314
+ Modal Footer (v1.0.57 정제 — 본문과 레이어 분리 + 부드러운 highlight)
1112
1315
  ============================================ */
1113
1316
  .rmm-footer {
1114
1317
  display: flex;
1115
1318
  align-items: center;
1116
1319
  justify-content: space-between;
1117
- padding: 0 16px;
1320
+ gap: 12px;
1321
+ padding: 0 18px;
1118
1322
  height: var(--rmm-footer-height);
1119
1323
  min-height: var(--rmm-footer-height);
1120
1324
  background: var(--rmm-footer-bg);
1121
1325
  border-top: 1px solid var(--rmm-footer-border);
1326
+ /* 상단 안쪽 1px 흰 하이라이트로 "내린 서랍" 느낌 + 모달 하단 radius 상속 */
1327
+ box-shadow: var(--rmm-footer-shadow, inset 0 1px 0 rgba(255, 255, 255, 0.6));
1328
+ border-bottom-left-radius: inherit;
1329
+ border-bottom-right-radius: inherit;
1122
1330
  flex-shrink: 0;
1123
1331
  }
1124
1332
 
1125
1333
  .rmm-footer-left {
1126
1334
  flex: 1;
1127
1335
  min-width: 0;
1336
+ display: flex;
1337
+ align-items: center;
1338
+ gap: 8px;
1128
1339
  }
1129
1340
 
1130
1341
  .rmm-footer-message {
1131
- font-size: 14px;
1342
+ font-size: 13px;
1132
1343
  color: var(--rmm-footer-text);
1344
+ letter-spacing: -0.01em;
1133
1345
  white-space: nowrap;
1134
1346
  overflow: hidden;
1135
1347
  text-overflow: ellipsis;
@@ -1142,7 +1354,7 @@
1142
1354
  }
1143
1355
 
1144
1356
  /* ============================================
1145
- Buttons
1357
+ Buttons (v1.0.57 정제 — 미세 lift · active 눌림 · focus 링)
1146
1358
  ============================================ */
1147
1359
  .rmm-btn {
1148
1360
  display: inline-flex;
@@ -1150,13 +1362,23 @@
1150
1362
  justify-content: center;
1151
1363
  gap: 6px;
1152
1364
  padding: 8px 16px;
1153
- font-size: 14px;
1154
- font-weight: 500;
1365
+ font-size: 13px;
1366
+ font-weight: 600;
1367
+ letter-spacing: -0.01em;
1155
1368
  border: none;
1156
- border-radius: 8px;
1369
+ border-radius: 7px;
1157
1370
  cursor: pointer;
1158
- transition: background-color 0.15s, opacity 0.15s;
1159
1371
  white-space: nowrap;
1372
+ transition: background-color 0.15s ease, transform 0.08s ease, box-shadow 0.15s ease, opacity 0.15s;
1373
+ }
1374
+
1375
+ .rmm-btn:active:not(:disabled) {
1376
+ transform: translateY(1px);
1377
+ }
1378
+
1379
+ .rmm-btn:focus-visible {
1380
+ outline: 2px solid var(--rmm-header-accent, #60a5fa);
1381
+ outline-offset: 2px;
1160
1382
  }
1161
1383
 
1162
1384
  .rmm-btn:disabled {
@@ -1167,19 +1389,23 @@
1167
1389
  .rmm-btn-primary {
1168
1390
  background: var(--rmm-btn-primary-bg);
1169
1391
  color: var(--rmm-btn-primary-text);
1392
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
1170
1393
  }
1171
1394
 
1172
1395
  .rmm-btn-primary:hover:not(:disabled) {
1173
1396
  background: var(--rmm-btn-primary-hover);
1397
+ box-shadow: 0 2px 4px rgba(37, 99, 235, 0.25);
1174
1398
  }
1175
1399
 
1176
1400
  .rmm-btn-secondary {
1177
1401
  background: var(--rmm-btn-secondary-bg);
1178
1402
  color: var(--rmm-btn-secondary-text);
1403
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.06);
1179
1404
  }
1180
1405
 
1181
1406
  .rmm-btn-secondary:hover:not(:disabled) {
1182
1407
  background: var(--rmm-btn-secondary-hover);
1408
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.1);
1183
1409
  }
1184
1410
 
1185
1411
  .rmm-btn-danger {
@@ -1601,16 +1827,19 @@
1601
1827
  display: flex;
1602
1828
  align-items: center;
1603
1829
  justify-content: center;
1604
- width: 18px;
1605
- height: 18px;
1830
+ width: 22px;
1831
+ height: 22px;
1832
+ border-radius: 5px;
1606
1833
  color: var(--rmm-header-text);
1607
- opacity: 0.4;
1834
+ opacity: 0.55;
1608
1835
  flex-shrink: 0;
1609
1836
  cursor: grab;
1837
+ transition: opacity 0.15s, background-color 0.15s;
1610
1838
  }
1611
1839
 
1612
1840
  .rmm-header.rmm-draggable .rmm-drag-handle:hover {
1613
- opacity: 0.7;
1841
+ opacity: 0.95;
1842
+ background: rgba(255, 255, 255, 0.12);
1614
1843
  }
1615
1844
 
1616
1845
  .rmm-drag-handle svg {
@@ -1834,8 +2063,8 @@
1834
2063
  남아 "잔향" 느낌. 또 기본 blur 를 8px + saturate 로 상향해 배경과의
1835
2064
  대비를 강화 (v2 overlay 와 동급). */
1836
2065
  .rmm-overlay {
1837
- backdrop-filter: blur(8px) saturate(1.1);
1838
- -webkit-backdrop-filter: blur(8px) saturate(1.1);
2066
+ backdrop-filter: blur(1.5px) saturate(1.1);
2067
+ -webkit-backdrop-filter: blur(1.5px) saturate(1.1);
1839
2068
  transition: opacity var(--rmm-dur-base) var(--rmm-ease-out),
1840
2069
  visibility var(--rmm-dur-base) var(--rmm-ease-out);
1841
2070
  }
@@ -17,26 +17,78 @@ export default class extends Controller {
17
17
  // Listen for sidebar toggled event to update icon
18
18
  this.handleSidebarToggled = this.handleSidebarToggled.bind(this)
19
19
  this.handleModalClosed = this.handleModalClosed.bind(this)
20
+ // v1.0.57 — 모바일 서브타이틀: 사이드바 메뉴 변경 이벤트 수신
21
+ this.handleSidebarItemSelect = this.handleSidebarItemSelect.bind(this)
20
22
 
21
23
  const modal = this.element.closest('.rmm-modal')
22
24
  if (modal) {
23
25
  modal.addEventListener('rmm-sidebar:toggled', this.handleSidebarToggled)
24
26
  modal.addEventListener('rmm:closed', this.handleModalClosed)
27
+ modal.addEventListener('rmm-sidebar:itemSelect', this.handleSidebarItemSelect)
25
28
  }
26
29
 
27
30
  // Title 더블클릭 이벤트 리스너 추가 및 원래 title 저장
28
31
  this.setupTitleEdit()
32
+ // 초기 sidebar 활성 항목 이름을 서브타이틀에 세팅
33
+ this.initSectionFromActiveSidebarItem()
29
34
  }
30
35
 
31
- setupTitleEdit() {
36
+ // 제목 텍스트를 담고 있는 노드. 하위 구조(<span.rmm-header-title-text>) 있으면 그것을, 없으면 h2 자체를 반환
37
+ _getTitleTextNode() {
32
38
  const titleEl = this.element.querySelector('.rmm-header-title')
33
- if (titleEl) {
34
- // 원래 title 저장 (모달 닫힐 때 복원용)
35
- this.originalTitle = titleEl.textContent.trim()
39
+ if (!titleEl) return null
40
+ return titleEl.querySelector('.rmm-header-title-text') || titleEl
41
+ }
36
42
 
37
- titleEl.addEventListener('dblclick', (e) => this.editTitle(e))
38
- titleEl.style.cursor = 'default'
43
+ setupTitleEdit() {
44
+ const titleEl = this.element.querySelector('.rmm-header-title')
45
+ const textEl = this._getTitleTextNode()
46
+ if (!titleEl || !textEl) return
47
+
48
+ // 원래 title 저장 (모달 닫힐 때 복원용)
49
+ this.originalTitle = textEl.textContent.trim()
50
+
51
+ // v1.0.57 — 모달이 "최소화 가능" 한 경우에만 타이틀 더블클릭 편집 활성화.
52
+ // 최소화 버튼 존재로 minimizable 여부를 판별 — data-action 에 minimize 가 있는 버튼 탐색
53
+ // (gem 내부 파티알은 .rmm-minimize-btn 클래스도 가지지만, 수동 HTML 은 클래스 없이 data-action 만 쓸 수도 있음).
54
+ // 부모 .rmm-header-title 는 드래그를 방해하지 않도록 pointer-events:none 유지하고,
55
+ // 편집 가능한 text span 만 선택적으로 pointer-events:auto 로 복원.
56
+ const isMinimizable = !!this.element.querySelector('.rmm-minimize-btn, [data-action*="rmm-header#minimize"]')
57
+ if (!isMinimizable) {
58
+ textEl.style.cursor = 'default'
59
+ return
39
60
  }
61
+
62
+ textEl.style.cursor = 'text'
63
+ textEl.style.pointerEvents = 'auto'
64
+ textEl.title = '더블클릭하여 제목 편집'
65
+ // span 에서 시작한 mousedown 이 헤더 드래그 로직과 충돌하지 않도록 bubble up 은 허용하되
66
+ // dblclick 만 편집 트리거로 사용
67
+ textEl.addEventListener('dblclick', (e) => this.editTitle(e))
68
+ }
69
+
70
+ // 모바일 서브타이틀 초기값 — sidebar 의 .rmm-active 항목 label 을 읽어 반영
71
+ initSectionFromActiveSidebarItem() {
72
+ const modal = this.element.closest('.rmm-modal')
73
+ if (!modal) return
74
+ const sidebar = modal.querySelector('.rmm-sidebar')
75
+ if (!sidebar) return
76
+ const active = sidebar.querySelector('.rmm-sidebar-item.rmm-active')
77
+ if (!active) return
78
+ const label = active.dataset.itemLabel || active.querySelector('.rmm-sidebar-item-label')?.textContent?.trim() || ''
79
+ this._setSectionText(label)
80
+ }
81
+
82
+ handleSidebarItemSelect(e) {
83
+ const detail = e.detail || {}
84
+ const label = detail.itemLabel || ''
85
+ this._setSectionText(label)
86
+ }
87
+
88
+ _setSectionText(text) {
89
+ const sectionEl = this.element.querySelector('.rmm-header-section')
90
+ if (!sectionEl) return
91
+ sectionEl.textContent = text || ''
40
92
  }
41
93
 
42
94
  disconnect() {
@@ -44,6 +96,7 @@ export default class extends Controller {
44
96
  if (modal) {
45
97
  modal.removeEventListener('rmm-sidebar:toggled', this.handleSidebarToggled)
46
98
  modal.removeEventListener('rmm:closed', this.handleModalClosed)
99
+ modal.removeEventListener('rmm-sidebar:itemSelect', this.handleSidebarItemSelect)
47
100
  }
48
101
  }
49
102
 
@@ -55,9 +108,9 @@ export default class extends Controller {
55
108
  resetTitle() {
56
109
  if (!this.originalTitle) return
57
110
 
58
- const titleEl = this.element.querySelector('.rmm-header-title')
59
- if (titleEl) {
60
- titleEl.textContent = this.originalTitle
111
+ const textEl = this._getTitleTextNode()
112
+ if (textEl) {
113
+ textEl.textContent = this.originalTitle
61
114
  }
62
115
  }
63
116
 
@@ -85,13 +138,17 @@ export default class extends Controller {
85
138
  }
86
139
 
87
140
  handleMouseDown(e) {
88
- // Only start drag from title bar area, not buttons
141
+ if (!this.draggableValue) return
142
+
143
+ // v1.0.57 — 드래그는 반드시 `.rmm-drag-handle` 에서 시작된 경우에만 동작.
144
+ // 헤더 다른 영역(타이틀/빈 공간/버튼)에서의 mousedown 으로는 드래그가 시작되지 않음.
145
+ if (!e.target.closest('.rmm-drag-handle')) return
146
+
147
+ // 드래그핸들 위에서라도 내부 버튼이 있다면 제외 (안전장치)
89
148
  if (e.target.closest('button') || e.target.closest('.rmm-header-controls')) {
90
149
  return
91
150
  }
92
151
 
93
- if (!this.draggableValue) return
94
-
95
152
  const controller = this.getModalController()
96
153
  if (controller) {
97
154
  controller.startDrag(e)
@@ -99,6 +156,8 @@ export default class extends Controller {
99
156
  }
100
157
 
101
158
  handleTouchStart(e) {
159
+ // v1.0.57 — 터치 드래그도 드래그핸들에서만 시작
160
+ if (!e.target.closest('.rmm-drag-handle')) return
102
161
  if (e.target.closest('button') || e.target.closest('.rmm-header-controls')) {
103
162
  return
104
163
  }
@@ -220,7 +279,10 @@ export default class extends Controller {
220
279
  // 이미 수정 중인 경우 무시
221
280
  if (titleEl.querySelector('input')) return
222
281
 
223
- const currentTitle = titleEl.textContent.trim()
282
+ // v1.0.57 하위 구조(.rmm-header-title-text)가 있으면 그 span 만 대상으로
283
+ // 편집. 서브타이틀(.rmm-header-section) 은 건드리지 않음.
284
+ const textEl = titleEl.querySelector('.rmm-header-title-text') || titleEl
285
+ const currentTitle = textEl.textContent.trim()
224
286
 
225
287
  // input 생성
226
288
  const input = document.createElement('input')
@@ -242,9 +304,9 @@ export default class extends Controller {
242
304
  outline: none;
243
305
  `
244
306
 
245
- // title 내용을 input으로 교체
246
- titleEl.textContent = ''
247
- titleEl.appendChild(input)
307
+ // 제목 텍스트 노드만 input으로 교체
308
+ textEl.textContent = ''
309
+ textEl.appendChild(input)
248
310
  input.focus()
249
311
  input.select()
250
312
 
@@ -254,7 +316,7 @@ export default class extends Controller {
254
316
  const finalTitle = newTitle || currentTitle
255
317
 
256
318
  // title 업데이트
257
- titleEl.textContent = finalTitle
319
+ textEl.textContent = finalTitle
258
320
 
259
321
  // modalStore의 title도 업데이트
260
322
  if (newTitle && newTitle !== currentTitle) {
@@ -273,7 +335,7 @@ export default class extends Controller {
273
335
  } else if (ke.key === 'Escape') {
274
336
  ke.preventDefault()
275
337
  input.removeEventListener('blur', saveTitle)
276
- titleEl.textContent = currentTitle
338
+ textEl.textContent = currentTitle
277
339
  }
278
340
  })
279
341
  }
@@ -81,6 +81,77 @@ export function initGlobalClickHandler() {
81
81
  })
82
82
  }
83
83
 
84
+ /**
85
+ * v1.0.57 — Global keyboard navigation for the top-most modal
86
+ *
87
+ * Ctrl + ArrowUp / ArrowDown → cycle active sidebar items
88
+ * Ctrl + ArrowLeft / ArrowRight → cycle active submenu tabs
89
+ *
90
+ * - Only the top-most open modal receives the shortcut.
91
+ * - Minimized modals are ignored.
92
+ * - When the focus is in an editable element (input / textarea / contenteditable),
93
+ * the shortcut is skipped so normal text navigation still works.
94
+ * - Sidebar + submenu group combination: the submenu of the currently active group
95
+ * is the navigation target.
96
+ */
97
+ export function initGlobalKeyboardHandler() {
98
+ document.addEventListener('keydown', (e) => {
99
+ if (!e.ctrlKey) return
100
+ const keys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
101
+ if (!keys.includes(e.key)) return
102
+
103
+ // Skip when typing in editable elements
104
+ const target = e.target
105
+ if (target && target.matches && target.matches('input, textarea, [contenteditable="true"], [contenteditable=""]')) {
106
+ return
107
+ }
108
+
109
+ const order = modalStore.getModalOrder()
110
+ if (order.length === 0) return
111
+ const topModalId = order[order.length - 1]
112
+ const config = modalStore.getModalConfig(topModalId)
113
+ // Skip if top modal is minimized (and not restored)
114
+ if (config && config.isMinimized && !config.isRestored) return
115
+
116
+ const modal = document.getElementById(topModalId)
117
+ if (!modal) return
118
+
119
+ // 양 끝에서 멈춤 (wraparound 없음). 이미 끝/처음이면 null 반환 → 이동 안 함.
120
+ const step = (items, forward) => {
121
+ const activeIndex = items.findIndex(el => el.classList.contains('rmm-active'))
122
+ const len = items.length
123
+ if (len === 0) return null
124
+ const baseIndex = activeIndex === -1 ? 0 : activeIndex
125
+ const nextIndex = forward ? baseIndex + 1 : baseIndex - 1
126
+ if (nextIndex < 0 || nextIndex >= len) return null
127
+ return items[nextIndex]
128
+ }
129
+
130
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
131
+ const sidebar = modal.querySelector('.rmm-sidebar')
132
+ if (!sidebar) return
133
+ const items = Array.from(sidebar.querySelectorAll('.rmm-sidebar-item')).filter(el => !el.classList.contains('rmm-disabled'))
134
+ const next = step(items, e.key === 'ArrowDown')
135
+ if (!next) return
136
+ e.preventDefault()
137
+ next.click()
138
+ return
139
+ }
140
+
141
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
142
+ // Prefer the submenu inside the currently active submenu group (if sidebar is used)
143
+ const activeGroup = modal.querySelector('.rmm-submenu-group.rmm-submenu-group-active')
144
+ const submenu = (activeGroup && activeGroup.querySelector('.rmm-submenu')) || modal.querySelector('.rmm-submenu')
145
+ if (!submenu) return
146
+ const items = Array.from(submenu.querySelectorAll('.rmm-submenu-item')).filter(el => !el.classList.contains('rmm-disabled'))
147
+ const next = step(items, e.key === 'ArrowRight')
148
+ if (!next) return
149
+ e.preventDefault()
150
+ next.click()
151
+ }
152
+ })
153
+ }
154
+
84
155
  /**
85
156
  * Open a modal by ID
86
157
  * If the modal is already open but minimized, it will be restored instead
@@ -11,7 +11,8 @@
11
11
  // ============================================
12
12
 
13
13
  export const MODAL_CONSTANTS = {
14
- DEFAULT_Z_INDEX: 2000,
14
+ // v1.0.57 — 모달이 최소화 태스크바(2100) 위에 오도록 시작 z-index 상향
15
+ DEFAULT_Z_INDEX: 2200,
15
16
  ANIMATION_DURATION: 200,
16
17
  HEADER_HEIGHT: 56,
17
18
  FOOTER_HEIGHT: 64,
@@ -81,7 +81,7 @@
81
81
  </svg>
82
82
  </button>
83
83
  <% end %>
84
- <h2 id="<%= modal_id %>-title" class="rmm-header-title"><%= title %></h2>
84
+ <h2 id="<%= modal_id %>-title" class="rmm-header-title"><span class="rmm-header-title-text"><%= title %></span><span class="rmm-header-section" data-rmm-header-target="section"></span></h2>
85
85
  </div>
86
86
 
87
87
  <div class="rmm-header-controls">
@@ -37,6 +37,7 @@
37
37
  # v1.0.50+: opt-in 탭별 footer — footer_buttons/footer_message 가 있으면
38
38
  # JSON 으로 직렬화해 data-rmm-footer 속성에 실어 보냄. submenu controller 가
39
39
  # selectItem 시 이 값을 읽어 setModalFooter() 로 footer 를 교체.
40
+ # v1.0.56+: type, form, data 필드 포함 (button[form] 기반 HTML5 submit 패턴 지원)
40
41
  submenu_footer_payload = if item[:footer_buttons].present? || item[:footer_message].present?
41
42
  {
42
43
  buttons: (item[:footer_buttons] || []).map { |b|
@@ -46,7 +47,10 @@
46
47
  disabled: b[:disabled] || false,
47
48
  loading: b[:loading] || false,
48
49
  action: b[:action],
49
- onclick: b[:onclick]
50
+ onclick: b[:onclick],
51
+ type: b[:type],
52
+ form: b[:form],
53
+ data: b[:data]
50
54
  }.compact
51
55
  },
52
56
  message: item[:footer_message]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsModalManager
4
- VERSION = "1.0.55"
4
+ VERSION = "1.0.57"
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.55
4
+ version: 1.0.57
5
5
  platform: ruby
6
6
  authors:
7
7
  - reshacs