rails_modal_manager 1.0.56 → 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 +4 -4
- data/app/assets/stylesheets/rails_modal_manager.css +335 -106
- data/app/javascript/rails_modal_manager/controllers/rmm_header_controller.js +80 -18
- data/app/javascript/rails_modal_manager/index.js +71 -0
- data/app/javascript/rails_modal_manager/modal_store.js +2 -1
- data/app/views/rails_modal_manager/_header.html.erb +1 -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: 91f46719ac2c4bf2aa6b8b33d883b85ee97f8dbc3490c7162441855ff6701e42
|
|
4
|
+
data.tar.gz: 881b2327c4fa56fa3fa4199655d26b3bb360b8e79ebdab4efec31db5d3368188
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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: #
|
|
43
|
-
--rmm-header-border: rgba(0, 0, 0, 0.
|
|
44
|
-
--rmm-header-text: #
|
|
45
|
-
--rmm-header-height:
|
|
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 (
|
|
48
|
-
--rmm-header-bg-depth-0: #
|
|
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: #
|
|
54
|
+
--rmm-header-bg-depth-1: #4b7099;
|
|
51
55
|
--rmm-header-text-depth-1: #ffffff;
|
|
52
|
-
--rmm-header-bg-depth-2: #
|
|
56
|
+
--rmm-header-bg-depth-2: #587fab;
|
|
53
57
|
--rmm-header-text-depth-2: #ffffff;
|
|
54
|
-
--rmm-header-bg-depth-3: #
|
|
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(
|
|
60
|
-
--rmm-footer-text: #
|
|
61
|
-
--rmm-footer-height:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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: #
|
|
69
|
-
--rmm-sidebar-icon: #
|
|
70
|
-
--rmm-sidebar-item-hover: rgba(
|
|
71
|
-
--rmm-sidebar-item-active-bg:
|
|
72
|
-
--rmm-sidebar-item-active-text: #
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
--rmm-submenu-
|
|
79
|
-
--rmm-submenu-
|
|
80
|
-
--rmm-submenu-
|
|
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:
|
|
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
|
-
|
|
141
|
-
--rmm-header-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
--rmm-header-
|
|
147
|
-
--rmm-header-
|
|
148
|
-
--rmm-header-
|
|
149
|
-
--rmm-header-
|
|
150
|
-
--rmm-header-
|
|
151
|
-
--rmm-header-
|
|
152
|
-
--rmm-header-
|
|
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.
|
|
156
|
-
--rmm-footer-text: #
|
|
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: #
|
|
159
|
-
--rmm-sidebar-border: rgba(255, 255, 255, 0.
|
|
160
|
-
--rmm-sidebar-text: #
|
|
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:
|
|
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: #
|
|
166
|
-
--rmm-submenu-container-bg: #
|
|
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.
|
|
169
|
-
--rmm-submenu-item-active-bg: #
|
|
170
|
-
--rmm-submenu-item-active-text: #
|
|
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
|
|
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
|
-
|
|
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:
|
|
425
|
+
cursor: default;
|
|
377
426
|
}
|
|
378
427
|
|
|
379
428
|
.rmm-header-left {
|
|
380
429
|
display: flex;
|
|
381
430
|
align-items: center;
|
|
382
|
-
gap:
|
|
383
|
-
flex:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
797
|
-
border-radius:
|
|
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:
|
|
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 (
|
|
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:
|
|
1116
|
+
align-items: flex-end;
|
|
954
1117
|
justify-content: flex-start;
|
|
955
|
-
gap:
|
|
956
|
-
padding:
|
|
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:
|
|
966
|
-
gap:
|
|
967
|
-
padding:
|
|
968
|
-
background:
|
|
969
|
-
border-radius:
|
|
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:
|
|
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:
|
|
991
|
-
font-size:
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
1154
|
-
font-weight:
|
|
1365
|
+
font-size: 13px;
|
|
1366
|
+
font-weight: 600;
|
|
1367
|
+
letter-spacing: -0.01em;
|
|
1155
1368
|
border: none;
|
|
1156
|
-
border-radius:
|
|
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:
|
|
1605
|
-
height:
|
|
1830
|
+
width: 22px;
|
|
1831
|
+
height: 22px;
|
|
1832
|
+
border-radius: 5px;
|
|
1606
1833
|
color: var(--rmm-header-text);
|
|
1607
|
-
opacity: 0.
|
|
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.
|
|
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(
|
|
1838
|
-
-webkit-backdrop-filter: blur(
|
|
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
|
-
|
|
36
|
+
// 제목 텍스트를 담고 있는 노드. 하위 구조(<span.rmm-header-title-text>)가 있으면 그것을, 없으면 h2 자체를 반환
|
|
37
|
+
_getTitleTextNode() {
|
|
32
38
|
const titleEl = this.element.querySelector('.rmm-header-title')
|
|
33
|
-
if (titleEl)
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
if (!titleEl) return null
|
|
40
|
+
return titleEl.querySelector('.rmm-header-title-text') || titleEl
|
|
41
|
+
}
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
59
|
-
if (
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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">
|