rails_modal_manager 1.0.48 → 1.0.50
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 +211 -15
- data/app/javascript/rails_modal_manager/controllers/rmm_modal_controller.js +36 -20
- data/app/javascript/rails_modal_manager/controllers/rmm_sidebar_controller.js +88 -20
- data/app/javascript/rails_modal_manager/controllers/rmm_submenu_controller.js +30 -0
- data/app/javascript/rails_modal_manager/footer_renderer.js +74 -0
- data/app/javascript/rails_modal_manager/index.js +3 -0
- data/app/views/rails_modal_manager/_submenu.html.erb +20 -0
- data/config/importmap.rb +1 -0
- data/lib/rails_modal_manager/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1580c0dddb5b53633cfdb943d27eab017b23498563308a4c7b163ce9654dfe2c
|
|
4
|
+
data.tar.gz: 498e99470357e2d5afc2b7efab7ff8f36af5e5edaad6d8d51e7a3128936e83ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f44e97638772289ca3c96c664f980d70795a1248656ff45ab408201881a04bf7e5570932d45d67f35c5980b7a7e42a6ec5dfc51cb419a0f10e663171952a063c
|
|
7
|
+
data.tar.gz: fb456bdde62080b5f5165c34ace756b6d13f79cdb8bedc82e8696a11a864ed61bc94531579097741d75c096bca891444db23376fd56d4f3d0478deaa2a346325
|
|
@@ -1866,38 +1866,40 @@
|
|
|
1866
1866
|
}
|
|
1867
1867
|
|
|
1868
1868
|
/* Position-aware transform — scale/translateY 를 transform 체인 **내부**에
|
|
1869
|
-
포함시켜 중심점 쏠림 방지. :not() 필터로
|
|
1870
|
-
|
|
1869
|
+
포함시켜 중심점 쏠림 방지. :not() 필터로 드래그/커스텀위치/최소화/size-full 제외.
|
|
1870
|
+
size-full 은 transform: none !important 를 유지하고 엔트리는 `translate` 개별
|
|
1871
|
+
프로퍼티(sheet 모션)가 담당. */
|
|
1872
|
+
.rmm-modal.rmm-position-center:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full) {
|
|
1871
1873
|
transform: translate(-50%, -50%) scale(0.94) translateY(14px);
|
|
1872
1874
|
}
|
|
1873
|
-
.rmm-modal.rmm-position-center.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized) {
|
|
1875
|
+
.rmm-modal.rmm-position-center.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full) {
|
|
1874
1876
|
transform: translate(-50%, -50%) scale(1) translateY(0);
|
|
1875
1877
|
}
|
|
1876
1878
|
|
|
1877
|
-
.rmm-modal.rmm-position-top:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized) {
|
|
1879
|
+
.rmm-modal.rmm-position-top:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full) {
|
|
1878
1880
|
transform: translateX(-50%) scale(0.94) translateY(-14px);
|
|
1879
1881
|
}
|
|
1880
|
-
.rmm-modal.rmm-position-top.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized) {
|
|
1882
|
+
.rmm-modal.rmm-position-top.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full) {
|
|
1881
1883
|
transform: translateX(-50%) scale(1) translateY(0);
|
|
1882
1884
|
}
|
|
1883
1885
|
|
|
1884
|
-
.rmm-modal.rmm-position-bottom:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized) {
|
|
1886
|
+
.rmm-modal.rmm-position-bottom:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full) {
|
|
1885
1887
|
transform: translateX(-50%) scale(0.94) translateY(14px);
|
|
1886
1888
|
}
|
|
1887
|
-
.rmm-modal.rmm-position-bottom.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized) {
|
|
1889
|
+
.rmm-modal.rmm-position-bottom.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full) {
|
|
1888
1890
|
transform: translateX(-50%) scale(1) translateY(0);
|
|
1889
1891
|
}
|
|
1890
1892
|
|
|
1891
|
-
.rmm-modal.rmm-position-top-left:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized),
|
|
1892
|
-
.rmm-modal.rmm-position-top-right:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized),
|
|
1893
|
-
.rmm-modal.rmm-position-bottom-left:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized),
|
|
1894
|
-
.rmm-modal.rmm-position-bottom-right:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized) {
|
|
1893
|
+
.rmm-modal.rmm-position-top-left:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full),
|
|
1894
|
+
.rmm-modal.rmm-position-top-right:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full),
|
|
1895
|
+
.rmm-modal.rmm-position-bottom-left:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full),
|
|
1896
|
+
.rmm-modal.rmm-position-bottom-right:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full) {
|
|
1895
1897
|
transform: scale(0.94) translateY(14px);
|
|
1896
1898
|
}
|
|
1897
|
-
.rmm-modal.rmm-position-top-left.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized),
|
|
1898
|
-
.rmm-modal.rmm-position-top-right.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized),
|
|
1899
|
-
.rmm-modal.rmm-position-bottom-left.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized),
|
|
1900
|
-
.rmm-modal.rmm-position-bottom-right.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized) {
|
|
1899
|
+
.rmm-modal.rmm-position-top-left.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full),
|
|
1900
|
+
.rmm-modal.rmm-position-top-right.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full),
|
|
1901
|
+
.rmm-modal.rmm-position-bottom-left.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full),
|
|
1902
|
+
.rmm-modal.rmm-position-bottom-right.rmm-active:not(.rmm-dragging):not(.rmm-custom-position):not(.rmm-minimized):not(.rmm-size-full) {
|
|
1901
1903
|
transform: scale(1) translateY(0);
|
|
1902
1904
|
}
|
|
1903
1905
|
|
|
@@ -1960,3 +1962,197 @@
|
|
|
1960
1962
|
display: none !important;
|
|
1961
1963
|
}
|
|
1962
1964
|
}
|
|
1965
|
+
|
|
1966
|
+
/* ============================================
|
|
1967
|
+
v1.0.48 — size=full 전환 플래시 제거 + 모바일 sheet 애니메이션
|
|
1968
|
+
============================================
|
|
1969
|
+
문제: mobile_default_maximized: true 모달은 JS 가 열릴 때 size-md → size-full
|
|
1970
|
+
로 클래스를 바꿈. 이 순간 transition 이 걸려 있어서 top/left/transform 이
|
|
1971
|
+
`translate(-50%,-50%) scale(0.94) translateY(14px)` → `none` 으로 보간되면서
|
|
1972
|
+
**모달이 중앙에서 좌상단으로 스르륵 미끄러지는 현상** 발생.
|
|
1973
|
+
|
|
1974
|
+
해결:
|
|
1975
|
+
(A) size-full 상태에서는 top/left/transform 의 transition 을 끊음.
|
|
1976
|
+
→ 중앙→좌상단 슬라이드 차단. opacity 만 페이드.
|
|
1977
|
+
(B) 모바일 + size-full 은 v2 의 `:sheet` variant 처럼 바닥에서 살짝
|
|
1978
|
+
올라오는 모션으로 대체. translate 개별 프로퍼티를 써서 기존
|
|
1979
|
+
`transform: none !important` 와 충돌 없음.
|
|
1980
|
+
*/
|
|
1981
|
+
|
|
1982
|
+
/* (A) size-full: 레이아웃 속성(top/left/transform) 전환 차단.
|
|
1983
|
+
opacity / box-shadow 만 전환되도록 transition 축소. */
|
|
1984
|
+
.rmm-modal.rmm-size-full {
|
|
1985
|
+
transition: opacity var(--rmm-dur-fast) var(--rmm-ease-in),
|
|
1986
|
+
visibility var(--rmm-dur-fast) var(--rmm-ease-in),
|
|
1987
|
+
box-shadow var(--rmm-dur-base) var(--rmm-ease-out) !important;
|
|
1988
|
+
}
|
|
1989
|
+
.rmm-modal.rmm-size-full.rmm-active {
|
|
1990
|
+
transition-duration: var(--rmm-dur-base) !important;
|
|
1991
|
+
transition-timing-function: var(--rmm-ease-emphasized) !important;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
/* (B) 모바일(≤768px) + size-full: translate 개별 프로퍼티로 sheet 느낌 엔트리.
|
|
1995
|
+
transform: none !important 는 그대로 두고 translate 만 보간. */
|
|
1996
|
+
@media (max-width: 768px) {
|
|
1997
|
+
.rmm-modal.rmm-size-full {
|
|
1998
|
+
translate: 0 24px;
|
|
1999
|
+
transition: opacity var(--rmm-dur-fast) var(--rmm-ease-in),
|
|
2000
|
+
visibility var(--rmm-dur-fast) var(--rmm-ease-in),
|
|
2001
|
+
translate var(--rmm-dur-fast) var(--rmm-ease-in),
|
|
2002
|
+
box-shadow var(--rmm-dur-base) var(--rmm-ease-out) !important;
|
|
2003
|
+
}
|
|
2004
|
+
.rmm-modal.rmm-size-full.rmm-active {
|
|
2005
|
+
translate: 0 0;
|
|
2006
|
+
transition-duration: var(--rmm-dur-base) !important;
|
|
2007
|
+
transition-timing-function: var(--rmm-ease-emphasized) !important;
|
|
2008
|
+
}
|
|
2009
|
+
/* 최소화/드래그 상태는 translate 개별 프로퍼티 초기화 (기존 로직 간섭 방지) */
|
|
2010
|
+
.rmm-modal.rmm-size-full.rmm-minimized,
|
|
2011
|
+
.rmm-modal.rmm-size-full.rmm-dragging {
|
|
2012
|
+
translate: 0 0;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/* (C) v1.0.50: 모바일+full 모달 여백은 JS inline !important 가 직접 담당 (rmm_modal_controller.js).
|
|
2016
|
+
CSS !important 규칙들(라인 1569/1574/1577)과의 specificity 싸움을 피하기 위해 CSS 규칙은 두지 않음.
|
|
2017
|
+
overflow:hidden 이 모달 본체에 이미 있어 내부 사이드바(absolute)도 둥근 모서리에 맞게 클리핑됨. */
|
|
2018
|
+
|
|
2019
|
+
/* (D) v1.0.50: 모바일에서는 모달 라운드 제거 (size 무관). PC 에서는 기본 radius 유지. */
|
|
2020
|
+
.rmm-modal {
|
|
2021
|
+
border-radius: 0 !important;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
/* ============================================
|
|
2026
|
+
v1.0.50 — 모달 외곽선 제거 + 폰트 선명도
|
|
2027
|
+
============================================
|
|
2028
|
+
v2 처럼 깔끔한 edge (box-shadow 만으로 구분).
|
|
2029
|
+
+ 한글 전용 폰트 stack 을 `.rmm-modal` 에 명시해 host 앱 (ORDARS 포함)이
|
|
2030
|
+
별도 폰트 지정을 안 해도 모달 내부는 한글이 선명하게 렌더되도록. */
|
|
2031
|
+
.rmm-modal {
|
|
2032
|
+
border: 0 !important;
|
|
2033
|
+
font-family:
|
|
2034
|
+
-apple-system, BlinkMacSystemFont,
|
|
2035
|
+
"Apple SD Gothic Neo",
|
|
2036
|
+
"Pretendard",
|
|
2037
|
+
"Noto Sans KR",
|
|
2038
|
+
"Malgun Gothic",
|
|
2039
|
+
"Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
|
2040
|
+
-webkit-font-smoothing: antialiased;
|
|
2041
|
+
-moz-osx-font-smoothing: grayscale;
|
|
2042
|
+
text-rendering: optimizeLegibility;
|
|
2043
|
+
}
|
|
2044
|
+
.rmm-overlay,
|
|
2045
|
+
.rmm-taskbar {
|
|
2046
|
+
font-family:
|
|
2047
|
+
-apple-system, BlinkMacSystemFont,
|
|
2048
|
+
"Apple SD Gothic Neo",
|
|
2049
|
+
"Pretendard",
|
|
2050
|
+
"Noto Sans KR",
|
|
2051
|
+
"Malgun Gothic",
|
|
2052
|
+
"Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
|
2053
|
+
-webkit-font-smoothing: antialiased;
|
|
2054
|
+
-moz-osx-font-smoothing: grayscale;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
/* ============================================
|
|
2058
|
+
v1.0.50 — 탭별 푸터 전환 애니메이션
|
|
2059
|
+
============================================
|
|
2060
|
+
submenu selectItem 시 setModalFooter() 가 .rmm-footer-left / -right 의
|
|
2061
|
+
innerHTML 을 교체. 교체 순간 미세한 fade 트랜지션을 주어 "딸깍" 느낌 제거. */
|
|
2062
|
+
.rmm-footer-left,
|
|
2063
|
+
.rmm-footer-right {
|
|
2064
|
+
transition: opacity var(--rmm-dur-fast) var(--rmm-ease-out);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/* ============================================
|
|
2068
|
+
v1.0.50 — 모바일 사이드바 단순화 (v2 스타일 peek)
|
|
2069
|
+
============================================
|
|
2070
|
+
모바일+최대화 상태에서의 사이드바:
|
|
2071
|
+
- 기본 hidden(완전 숨김). 토글 버튼으로 expanded drawer 오픈 (2상태).
|
|
2072
|
+
- 모달 오픈 시 1회 peek 애니메이션: 얇은 drawer(아이콘만 표시)가 좌측에서
|
|
2073
|
+
0 → 100% → 100% → -100% 로 슬라이드해 "사이드바가 여기 있다" 라는 힌트만 줌.
|
|
2074
|
+
- 이전의 'icons'(64px inline) 단계는 제거. 관련 CSS(.rmm-sidebar-collapsed)는
|
|
2075
|
+
PC 의 접힘 기능용으로 유지되므로 삭제하지 않음.
|
|
2076
|
+
*/
|
|
2077
|
+
@keyframes rmm-sidebar-peek {
|
|
2078
|
+
0% { transform: translateX(-100%); }
|
|
2079
|
+
30% { transform: translateX(0); }
|
|
2080
|
+
55% { transform: translateX(0); }
|
|
2081
|
+
100% { transform: translateX(-100%); }
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
@media (max-width: 768px) {
|
|
2085
|
+
/* ── peek 중인 사이드바: 얇은 폭(아이콘 너비) + 오버레이 + 1회 animation ── */
|
|
2086
|
+
.rmm-modal.rmm-size-full .rmm-sidebar[data-peek="true"] {
|
|
2087
|
+
position: absolute !important;
|
|
2088
|
+
top: var(--rmm-header-height) !important;
|
|
2089
|
+
left: 0 !important;
|
|
2090
|
+
bottom: 0 !important;
|
|
2091
|
+
z-index: 11 !important;
|
|
2092
|
+
width: var(--rmm-sidebar-collapsed-width) !important;
|
|
2093
|
+
min-width: var(--rmm-sidebar-collapsed-width) !important;
|
|
2094
|
+
padding: 8px 6px !important;
|
|
2095
|
+
pointer-events: none; /* peek 중엔 클릭 불가 — 단순 힌트 */
|
|
2096
|
+
box-shadow: var(--rmm-elev-drag);
|
|
2097
|
+
animation: rmm-sidebar-peek 1000ms var(--rmm-ease-out) forwards;
|
|
2098
|
+
transition: none !important; /* animation 과 transition 충돌 방지 */
|
|
2099
|
+
}
|
|
2100
|
+
.rmm-modal.rmm-size-full .rmm-sidebar[data-peek="true"] .rmm-sidebar-item-label,
|
|
2101
|
+
.rmm-modal.rmm-size-full .rmm-sidebar[data-peek="true"] .rmm-sidebar-item-badge {
|
|
2102
|
+
display: none;
|
|
2103
|
+
}
|
|
2104
|
+
.rmm-modal.rmm-size-full .rmm-sidebar[data-peek="true"] .rmm-sidebar-item {
|
|
2105
|
+
justify-content: center;
|
|
2106
|
+
padding: 10px 8px;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
/* ── 모바일+full 사이드바 일반 상태: transform translateX 기반 drawer + 부드러운 transition ──
|
|
2110
|
+
기존 width:0 / width:64px 스냅 방식(__라인 906~945__)을 덮어써 전환을 부드럽게 만든다.
|
|
2111
|
+
peek 중에는 이 규칙이 비활성(:not([data-peek="true"])). */
|
|
2112
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]) {
|
|
2113
|
+
position: absolute !important;
|
|
2114
|
+
top: var(--rmm-header-height) !important;
|
|
2115
|
+
left: 0 !important;
|
|
2116
|
+
bottom: 0 !important;
|
|
2117
|
+
z-index: 10 !important;
|
|
2118
|
+
width: var(--rmm-sidebar-width) !important;
|
|
2119
|
+
min-width: var(--rmm-sidebar-width) !important;
|
|
2120
|
+
padding: 8px 6px !important;
|
|
2121
|
+
overflow-y: auto;
|
|
2122
|
+
border-right: 1px solid var(--rmm-sidebar-border) !important;
|
|
2123
|
+
transform: translateX(-100%);
|
|
2124
|
+
transition: transform var(--rmm-dur-base) var(--rmm-ease-emphasized),
|
|
2125
|
+
box-shadow var(--rmm-dur-base) var(--rmm-ease-out);
|
|
2126
|
+
box-shadow: none;
|
|
2127
|
+
}
|
|
2128
|
+
/* expanded = hidden 도 collapsed 도 없을 때 → drawer 펼침 */
|
|
2129
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]):not(.rmm-sidebar-hidden):not(.rmm-sidebar-collapsed) {
|
|
2130
|
+
transform: translateX(0);
|
|
2131
|
+
box-shadow: var(--rmm-elev-drag);
|
|
2132
|
+
}
|
|
2133
|
+
/* hidden 과 legacy collapsed 모두 동일하게 숨김 (v1.0.50 에선 icons 단계 제거됨) */
|
|
2134
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]).rmm-sidebar-hidden,
|
|
2135
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]).rmm-sidebar-collapsed {
|
|
2136
|
+
transform: translateX(-100%) !important;
|
|
2137
|
+
box-shadow: none !important;
|
|
2138
|
+
}
|
|
2139
|
+
/* expanded 일 때 라벨/배지 확실히 표시 (peek 스타일이 섞여 있을 수 있으므로 복원) */
|
|
2140
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]) .rmm-sidebar-item-label,
|
|
2141
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]) .rmm-sidebar-item-badge {
|
|
2142
|
+
display: inline-block;
|
|
2143
|
+
}
|
|
2144
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]) .rmm-sidebar-item {
|
|
2145
|
+
justify-content: flex-start;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
/* 오버레이: expanded 일 때 표시, 나머진 숨김 (transform 전환과 동기) */
|
|
2149
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]):not(.rmm-sidebar-hidden):not(.rmm-sidebar-collapsed) ~ .rmm-sidebar-overlay {
|
|
2150
|
+
opacity: 1 !important;
|
|
2151
|
+
visibility: visible !important;
|
|
2152
|
+
}
|
|
2153
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]).rmm-sidebar-hidden ~ .rmm-sidebar-overlay,
|
|
2154
|
+
.rmm-modal.rmm-size-full .rmm-sidebar:not([data-peek="true"]).rmm-sidebar-collapsed ~ .rmm-sidebar-overlay {
|
|
2155
|
+
opacity: 0 !important;
|
|
2156
|
+
visibility: hidden !important;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
@@ -536,33 +536,49 @@ export default class extends Controller {
|
|
|
536
536
|
|
|
537
537
|
// For full size, explicitly set full screen styles
|
|
538
538
|
if (config.size === 'full' || config.isMaximized) {
|
|
539
|
-
|
|
539
|
+
const isMobile = window.innerWidth <= 768
|
|
540
540
|
const minimizedGroups = modalStore.getMinimizedModalGroups()
|
|
541
541
|
const hasMinimizedModals = minimizedGroups.length > 0
|
|
542
|
-
// CSS 변수에서 taskbar 높이 가져오기 (기본값 48px)
|
|
543
542
|
const taskbarHeight = hasMinimizedModals
|
|
544
543
|
? parseInt(getComputedStyle(document.documentElement).getPropertyValue('--rmm-taskbar-height') || '48', 10)
|
|
545
544
|
: 0
|
|
546
545
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
modal.style.setProperty('height', `calc(
|
|
560
|
-
modal.style.setProperty('
|
|
561
|
-
modal.style.setProperty('
|
|
546
|
+
if (isMobile) {
|
|
547
|
+
// v1.0.50: 모바일 full 은 뷰포트에 딱 붙이지 않고 8px 여백 + border-radius 유지.
|
|
548
|
+
// JS inline !important 로 직접 설정해 기존 CSS !important 규칙들과의 specificity 싸움 원천 차단.
|
|
549
|
+
const bottomTaskbar = taskbarHeight > 0 ? 8 + taskbarHeight : 8
|
|
550
|
+
modal.style.setProperty('top', '8px', 'important')
|
|
551
|
+
modal.style.setProperty('left', '8px', 'important')
|
|
552
|
+
modal.style.setProperty('right', '8px', 'important')
|
|
553
|
+
modal.style.setProperty('bottom', `${bottomTaskbar}px`, 'important')
|
|
554
|
+
modal.style.setProperty('width', 'calc(100vw - 16px)', 'important')
|
|
555
|
+
modal.style.setProperty('min-width', 'calc(100vw - 16px)', 'important')
|
|
556
|
+
modal.style.setProperty('max-width', 'calc(100vw - 16px)', 'important')
|
|
557
|
+
modal.style.setProperty('height', `calc(100dvh - ${8 + bottomTaskbar}px)`, 'important')
|
|
558
|
+
modal.style.setProperty('max-height', `calc(100dvh - ${8 + bottomTaskbar}px)`, 'important')
|
|
559
|
+
modal.style.setProperty('border-radius', '0', 'important')
|
|
560
|
+
modal.style.setProperty('transform', 'none', 'important')
|
|
561
|
+
modal.style.setProperty('margin', '0', 'important')
|
|
562
562
|
} else {
|
|
563
|
-
|
|
564
|
-
modal.style.setProperty('
|
|
565
|
-
modal.style.setProperty('
|
|
563
|
+
// 데스크톱: 기존대로 전체 덮기
|
|
564
|
+
modal.style.setProperty('width', '100%', 'important')
|
|
565
|
+
modal.style.setProperty('min-width', '100%', 'important')
|
|
566
|
+
modal.style.setProperty('top', '0', 'important')
|
|
567
|
+
modal.style.setProperty('left', '0', 'important')
|
|
568
|
+
modal.style.setProperty('right', '0', 'important')
|
|
569
|
+
modal.style.setProperty('transform', 'none', 'important')
|
|
570
|
+
modal.style.setProperty('border-radius', '0', 'important')
|
|
571
|
+
modal.style.setProperty('margin', '0', 'important')
|
|
572
|
+
|
|
573
|
+
if (taskbarHeight > 0) {
|
|
574
|
+
modal.style.setProperty('height', `calc(100% - ${taskbarHeight}px)`, 'important')
|
|
575
|
+
modal.style.setProperty('max-height', `calc(100% - ${taskbarHeight}px)`, 'important')
|
|
576
|
+
modal.style.setProperty('bottom', `${taskbarHeight}px`, 'important')
|
|
577
|
+
} else {
|
|
578
|
+
modal.style.setProperty('height', '100%', 'important')
|
|
579
|
+
modal.style.setProperty('max-height', '100%', 'important')
|
|
580
|
+
modal.style.setProperty('bottom', '0', 'important')
|
|
581
|
+
}
|
|
566
582
|
}
|
|
567
583
|
} else {
|
|
568
584
|
// Reset full-size specific styles
|
|
@@ -26,27 +26,35 @@ export default class extends Controller {
|
|
|
26
26
|
static values = {
|
|
27
27
|
modalId: String,
|
|
28
28
|
collapsed: { type: Boolean, default: false },
|
|
29
|
-
// 모바일+최대화 상태에서의 사이드바
|
|
30
|
-
|
|
29
|
+
// v1.0.50+: 모바일+최대화 상태에서의 사이드바 모드.
|
|
30
|
+
// 예전에는 'expanded' | 'icons' | 'hidden' 3단계였으나, v2 스타일에 맞춰
|
|
31
|
+
// 2단계(expanded | hidden)로 단순화. 기본은 hidden(숨김) + 오픈 직후 peek.
|
|
32
|
+
sidebarMode: { type: String, default: 'hidden' },
|
|
31
33
|
// 이미 선택된 항목을 다시 클릭했을 때 이벤트 발생 여부
|
|
32
34
|
refreshOnReselect: { type: Boolean, default: true },
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
connect() {
|
|
36
38
|
this.currentItemId = null
|
|
39
|
+
this._peekDone = false
|
|
37
40
|
|
|
38
41
|
// 모바일 사이즈(768px 미만)에서는 기본적으로 사이드바를 닫힌 상태로 시작
|
|
39
42
|
if (window.innerWidth < 768) {
|
|
40
43
|
this.collapsedValue = true
|
|
41
|
-
// 모바일+최대화 상태 체크하여 초기 모드 설정
|
|
42
|
-
if (this.isMobileMaximized()) {
|
|
43
|
-
this.sidebarModeValue = 'icons'
|
|
44
|
-
this.applySidebarMode()
|
|
45
|
-
}
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
// v1.0.50+: 모바일+최대화 가 **현재** 또는 **나중에** 성립하면 peek + hidden 으로 전환.
|
|
47
|
+
// 섹션 2 처럼 동적 모달이 size-md → size-full 로 뒤늦게 바뀌는 경우도 대응하기 위해
|
|
48
|
+
// MutationObserver 로 modal 의 class 변경을 감시.
|
|
49
|
+
this._setupMobilePeekObserver()
|
|
50
|
+
|
|
51
|
+
// 첫 렌더 기준으로 이미 peek 조건을 만족하면 즉시 실행, 아니면 fallback
|
|
52
|
+
// (collapsed 클래스 부여는 peek 발동 여부에 따라 분기)
|
|
53
|
+
if (!this._runPeekIfReady()) {
|
|
54
|
+
// 모바일이지만 아직 mobile+maximized 가 아님 → 일반 collapsed 로 시작
|
|
55
|
+
if (this.collapsedValue) {
|
|
56
|
+
this.element.classList.add('rmm-sidebar-collapsed')
|
|
57
|
+
}
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
// Find initial active item
|
|
@@ -67,6 +75,39 @@ export default class extends Controller {
|
|
|
67
75
|
disconnect() {
|
|
68
76
|
this.element.removeEventListener('rmm-sidebar:toggle', this.handleToggleEvent)
|
|
69
77
|
this.element.removeEventListener('rmm-sidebar:closeOverlay', this.handleCloseOverlayEvent)
|
|
78
|
+
this._peekObserver?.disconnect()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* v1.0.50+ 모바일 peek 준비. modal 의 class 가 나중에 rmm-size-full / rmm-active 로
|
|
83
|
+
* 바뀌는 경우도 대응. 조건 성립 즉시 peek 실행 후 observer 해제.
|
|
84
|
+
*/
|
|
85
|
+
_setupMobilePeekObserver() {
|
|
86
|
+
if (window.innerWidth >= 768) return
|
|
87
|
+
const modal = this.element.closest('.rmm-modal')
|
|
88
|
+
if (!modal) return
|
|
89
|
+
this._peekObserver = new MutationObserver(() => this._runPeekIfReady())
|
|
90
|
+
this._peekObserver.observe(modal, { attributes: true, attributeFilter: ['class'] })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_runPeekIfReady() {
|
|
94
|
+
if (this._peekDone) return true
|
|
95
|
+
if (window.innerWidth >= 768) return false
|
|
96
|
+
if (!this.isMobileMaximized()) return false
|
|
97
|
+
const modal = this.element.closest('.rmm-modal')
|
|
98
|
+
if (!modal) return false
|
|
99
|
+
// rmm-active 가 붙은(= 실제로 보이는) 타이밍까지 기다린다
|
|
100
|
+
if (!modal.classList.contains('rmm-active')) return false
|
|
101
|
+
|
|
102
|
+
// 기존 legacy collapsed 클래스 정리 후 hidden 으로 진입
|
|
103
|
+
this.element.classList.remove('rmm-sidebar-collapsed')
|
|
104
|
+
this.sidebarModeValue = 'hidden'
|
|
105
|
+
this.applySidebarMode()
|
|
106
|
+
this._triggerMobilePeek()
|
|
107
|
+
this._peekDone = true
|
|
108
|
+
this._peekObserver?.disconnect()
|
|
109
|
+
this._peekObserver = null
|
|
110
|
+
return true
|
|
70
111
|
}
|
|
71
112
|
|
|
72
113
|
toggle() {
|
|
@@ -112,12 +153,14 @@ export default class extends Controller {
|
|
|
112
153
|
}
|
|
113
154
|
|
|
114
155
|
/**
|
|
115
|
-
* 모바일+최대화 상태에서의
|
|
116
|
-
* expanded (펼침)
|
|
156
|
+
* 모바일+최대화 상태에서의 토글 (v1.0.50+: 2단계로 단순화)
|
|
157
|
+
* expanded (펼침) ↔ hidden (숨김) — 과거 'icons' 단계 제거
|
|
117
158
|
*/
|
|
118
159
|
toggleMobileMaximized() {
|
|
119
|
-
const modes = ['expanded', '
|
|
120
|
-
|
|
160
|
+
const modes = ['expanded', 'hidden']
|
|
161
|
+
// 이전 'icons' 값이 어떤 경로로 들어온 경우도 hidden 과 같게 취급
|
|
162
|
+
const current = this.sidebarModeValue === 'expanded' ? 'expanded' : 'hidden'
|
|
163
|
+
const currentIndex = modes.indexOf(current)
|
|
121
164
|
const nextIndex = (currentIndex + 1) % modes.length
|
|
122
165
|
this.sidebarModeValue = modes[nextIndex]
|
|
123
166
|
|
|
@@ -183,10 +226,12 @@ export default class extends Controller {
|
|
|
183
226
|
const hiddenIcon = toggleBtn.querySelector('.rmm-icon-hidden')
|
|
184
227
|
|
|
185
228
|
if (this.isMobileMaximized()) {
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (
|
|
229
|
+
// v1.0.50+: 모바일+최대화도 2단계 아이콘 (expanded ↔ hidden).
|
|
230
|
+
// expanded 시 접힘 아이콘(◂), hidden 시 펼침 아이콘(☰/▸)
|
|
231
|
+
const isExpanded = this.sidebarModeValue === 'expanded'
|
|
232
|
+
if (collapseIcon) collapseIcon.style.display = isExpanded ? 'block' : 'none'
|
|
233
|
+
if (expandIcon) expandIcon.style.display = isExpanded ? 'none' : 'block'
|
|
234
|
+
if (hiddenIcon) hiddenIcon.style.display = 'none'
|
|
190
235
|
} else {
|
|
191
236
|
// 일반: 2단계 아이콘
|
|
192
237
|
if (collapseIcon) collapseIcon.style.display = this.collapsedValue ? 'none' : 'block'
|
|
@@ -195,6 +240,31 @@ export default class extends Controller {
|
|
|
195
240
|
}
|
|
196
241
|
}
|
|
197
242
|
|
|
243
|
+
/**
|
|
244
|
+
* v1.0.50+: 모바일+최대화 모달이 열릴 때 사이드바가 있다는 힌트로
|
|
245
|
+
* 얇은 drawer(아이콘만 보이는 폭)가 한 번 미끄러졌다 사라지는 peek 애니메이션.
|
|
246
|
+
* CSS @keyframes rmm-sidebar-peek 이 실제 모션을 담당하고, 여기서는 data-peek
|
|
247
|
+
* 속성 토글 + animationend 후 자동 제거만 처리.
|
|
248
|
+
*/
|
|
249
|
+
_triggerMobilePeek() {
|
|
250
|
+
// 이미 peek 중이면 중복 트리거 방지
|
|
251
|
+
if (this.element.dataset.peek === 'true') return
|
|
252
|
+
requestAnimationFrame(() => {
|
|
253
|
+
this.element.dataset.peek = 'true'
|
|
254
|
+
const onEnd = (ev) => {
|
|
255
|
+
if (ev.animationName && ev.animationName.indexOf('rmm-sidebar-peek') !== -1) {
|
|
256
|
+
delete this.element.dataset.peek
|
|
257
|
+
this.element.removeEventListener('animationend', onEnd)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
this.element.addEventListener('animationend', onEnd)
|
|
261
|
+
// 안전장치: 1.5초 후에도 속성이 남아있으면 강제 제거 (reduced-motion 등)
|
|
262
|
+
setTimeout(() => {
|
|
263
|
+
if (this.element.dataset.peek === 'true') delete this.element.dataset.peek
|
|
264
|
+
}, 1500)
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
198
268
|
selectItem(e) {
|
|
199
269
|
e.preventDefault()
|
|
200
270
|
e.stopPropagation()
|
|
@@ -242,13 +312,11 @@ export default class extends Controller {
|
|
|
242
312
|
// On mobile, close sidebar after selection
|
|
243
313
|
if (window.innerWidth < 768) {
|
|
244
314
|
if (this.isMobileMaximized()) {
|
|
245
|
-
// expanded
|
|
246
|
-
// icons 상태에서 클릭하면 icons 상태 유지
|
|
315
|
+
// v1.0.50+: 2상태 — expanded 에서 클릭 시 hidden 으로 닫힘 (icons 단계 제거됨)
|
|
247
316
|
if (this.sidebarModeValue === 'expanded') {
|
|
248
317
|
this.sidebarModeValue = 'hidden'
|
|
249
318
|
this.applySidebarMode()
|
|
250
319
|
}
|
|
251
|
-
// icons 상태면 그대로 유지
|
|
252
320
|
} else {
|
|
253
321
|
this.collapsedValue = true
|
|
254
322
|
this.element.classList.add('rmm-sidebar-collapsed')
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { setModalFooter } from "rails_modal_manager/footer_renderer"
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Rails Modal Manager - Submenu Controller
|
|
@@ -50,6 +51,9 @@ export default class extends Controller {
|
|
|
50
51
|
const activeItem = this.element.querySelector('.rmm-submenu-item.rmm-active')
|
|
51
52
|
if (activeItem) {
|
|
52
53
|
this.currentItemId = activeItem.dataset.itemId
|
|
54
|
+
// v1.0.50+: 초기 active 탭에 탭별 footer 가 지정돼 있으면 바로 반영
|
|
55
|
+
// (모달 레벨 footer_buttons 로 렌더된 기본값을 override)
|
|
56
|
+
this._applyFooterFromItem(activeItem)
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -94,6 +98,10 @@ export default class extends Controller {
|
|
|
94
98
|
this.switchPanel(item)
|
|
95
99
|
}
|
|
96
100
|
|
|
101
|
+
// v1.0.50+: opt-in 탭별 footer — data-rmm-footer 가 있을 때만 교체.
|
|
102
|
+
// 없으면 기존 정적 footer 가 그대로 유지됨 (기존 동작 보존).
|
|
103
|
+
this._applyFooterFromItem(item)
|
|
104
|
+
|
|
97
105
|
// Dispatch event
|
|
98
106
|
this.dispatch('itemSelect', {
|
|
99
107
|
detail: {
|
|
@@ -105,6 +113,25 @@ export default class extends Controller {
|
|
|
105
113
|
})
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
/**
|
|
117
|
+
* submenu item 의 data-rmm-footer JSON 을 읽어 해당 탭의 footer 로 교체.
|
|
118
|
+
* 속성이 없으면 아무 일도 안 함 (opt-in).
|
|
119
|
+
*/
|
|
120
|
+
_applyFooterFromItem(item) {
|
|
121
|
+
const raw = item.dataset.rmmFooter
|
|
122
|
+
if (!raw) return
|
|
123
|
+
let payload
|
|
124
|
+
try {
|
|
125
|
+
payload = JSON.parse(raw)
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.warn('[rmm-submenu] invalid data-rmm-footer JSON:', err)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
const modal = this.element.closest('.rmm-modal')
|
|
131
|
+
if (!modal) return
|
|
132
|
+
setModalFooter(modal, payload.buttons || [], payload.message || null)
|
|
133
|
+
}
|
|
134
|
+
|
|
108
135
|
/**
|
|
109
136
|
* Force reload the active submenu item's content
|
|
110
137
|
* Called by sidebar controller when switching sidebar items
|
|
@@ -120,6 +147,9 @@ export default class extends Controller {
|
|
|
120
147
|
this.switchPanel(activeItem)
|
|
121
148
|
}
|
|
122
149
|
|
|
150
|
+
// v1.0.50+: sidebar 전환으로 active submenu 세트가 바뀐 경우에도 footer 동기화
|
|
151
|
+
this._applyFooterFromItem(activeItem)
|
|
152
|
+
|
|
123
153
|
// Dispatch event so custom controllers can also handle the content load
|
|
124
154
|
this.dispatch('itemSelect', {
|
|
125
155
|
detail: {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rails Modal Manager — Footer Renderer (v1.0.50+)
|
|
3
|
+
*
|
|
4
|
+
* 탭별로 모달 푸터(하단 버튼 영역)를 동적으로 교체하기 위한 헬퍼.
|
|
5
|
+
* opt-in: submenu item 에 `data-rmm-footer='{"buttons":[...],"message":"..."}'`
|
|
6
|
+
* 속성이 있을 때만 submenu controller 가 이 함수를 호출.
|
|
7
|
+
*
|
|
8
|
+
* DOM 구조는 _footer.html.erb 와 완전히 동일하게 생성해 기존 CSS 가 그대로 먹음.
|
|
9
|
+
* 모달에 .rmm-footer 요소가 없으면 아무것도 하지 않음(no-op).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
function escapeHtml(str) {
|
|
13
|
+
if (str == null) return ""
|
|
14
|
+
return String(str)
|
|
15
|
+
.replace(/&/g, "&")
|
|
16
|
+
.replace(/</g, "<")
|
|
17
|
+
.replace(/>/g, ">")
|
|
18
|
+
.replace(/"/g, """)
|
|
19
|
+
.replace(/'/g, "'")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function renderButtonHtml(btn) {
|
|
23
|
+
const classes = ["rmm-btn", `rmm-btn-${btn.variant || "secondary"}`]
|
|
24
|
+
if (btn.loading) classes.push("rmm-btn-loading")
|
|
25
|
+
|
|
26
|
+
const attrs = [`class="${classes.join(" ")}"`]
|
|
27
|
+
if (btn.disabled || btn.loading) attrs.push("disabled")
|
|
28
|
+
if (btn.action) attrs.push(`data-action="${escapeHtml(btn.action)}"`)
|
|
29
|
+
if (btn.id) attrs.push(`data-button-id="${escapeHtml(btn.id)}"`)
|
|
30
|
+
|
|
31
|
+
return `<button type="button" ${attrs.join(" ")}>${escapeHtml(btn.label || "")}</button>`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 모달의 footer 영역을 주어진 buttons / message 로 교체.
|
|
36
|
+
*
|
|
37
|
+
* @param {HTMLElement|string} modal - `.rmm-modal` 엘리먼트 또는 modal id
|
|
38
|
+
* @param {Array<Object>} buttons - [{id, label, variant, disabled, loading, action}]
|
|
39
|
+
* @param {string} [message] - footer 좌측 메시지 (optional)
|
|
40
|
+
* @returns {boolean} 실제로 교체되었는지
|
|
41
|
+
*/
|
|
42
|
+
export function setModalFooter(modal, buttons = [], message = null) {
|
|
43
|
+
const modalEl = typeof modal === "string" ? document.getElementById(modal) : modal
|
|
44
|
+
if (!modalEl) return false
|
|
45
|
+
|
|
46
|
+
const footer = modalEl.querySelector(".rmm-footer")
|
|
47
|
+
if (!footer) return false
|
|
48
|
+
|
|
49
|
+
const left = footer.querySelector(".rmm-footer-left")
|
|
50
|
+
const right = footer.querySelector(".rmm-footer-right")
|
|
51
|
+
if (!left || !right) return false
|
|
52
|
+
|
|
53
|
+
// 좌측 메시지 영역
|
|
54
|
+
if (message && String(message).length > 0) {
|
|
55
|
+
left.innerHTML = `<span class="rmm-footer-message">${escapeHtml(message)}</span>`
|
|
56
|
+
} else {
|
|
57
|
+
left.innerHTML = ""
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 우측 버튼 영역
|
|
61
|
+
right.innerHTML = (buttons || []).map(renderButtonHtml).join("")
|
|
62
|
+
|
|
63
|
+
// 교체 이벤트 dispatch (앱에서 후처리 원할 경우용)
|
|
64
|
+
footer.dispatchEvent(
|
|
65
|
+
new CustomEvent("rmm-footer:updated", {
|
|
66
|
+
bubbles: true,
|
|
67
|
+
detail: { modalId: modalEl.id, buttons, message }
|
|
68
|
+
})
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default setModalFooter
|
|
@@ -13,10 +13,13 @@ export const VERSION = "1.0.34"
|
|
|
13
13
|
// Import core modules for internal use
|
|
14
14
|
import modalStore, { MODAL_CONSTANTS, SIZE_CONFIG, POSITION_CONFIG, CASCADE_OFFSET, modalSizeStorage, modalUtils } from "rails_modal_manager/modal_store"
|
|
15
15
|
import historyStackManager from "rails_modal_manager/history_stack_manager"
|
|
16
|
+
import { setModalFooter } from "rails_modal_manager/footer_renderer"
|
|
16
17
|
|
|
17
18
|
// Re-export core modules
|
|
18
19
|
export { modalStore, MODAL_CONSTANTS, SIZE_CONFIG, POSITION_CONFIG, CASCADE_OFFSET, modalSizeStorage, modalUtils }
|
|
19
20
|
export { historyStackManager }
|
|
21
|
+
// v1.0.50+: 탭별 footer 교체 API (opt-in). 외부에서 수동 제어 시에도 사용 가능.
|
|
22
|
+
export { setModalFooter }
|
|
20
23
|
|
|
21
24
|
// Stimulus controllers
|
|
22
25
|
export { default as RmmModalController } from "rails_modal_manager/controllers/rmm_modal_controller"
|
|
@@ -33,6 +33,25 @@
|
|
|
33
33
|
item_classes << "rmm-active" if item[:active]
|
|
34
34
|
item_classes << "rmm-disabled" if item[:disabled]
|
|
35
35
|
%>
|
|
36
|
+
<%
|
|
37
|
+
# v1.0.50+: opt-in 탭별 footer — footer_buttons/footer_message 가 있으면
|
|
38
|
+
# JSON 으로 직렬화해 data-rmm-footer 속성에 실어 보냄. submenu controller 가
|
|
39
|
+
# selectItem 시 이 값을 읽어 setModalFooter() 로 footer 를 교체.
|
|
40
|
+
submenu_footer_payload = if item[:footer_buttons].present? || item[:footer_message].present?
|
|
41
|
+
{
|
|
42
|
+
buttons: (item[:footer_buttons] || []).map { |b|
|
|
43
|
+
{
|
|
44
|
+
id: b[:id], label: b[:label],
|
|
45
|
+
variant: b[:variant] || "secondary",
|
|
46
|
+
disabled: b[:disabled] || false,
|
|
47
|
+
loading: b[:loading] || false,
|
|
48
|
+
action: b[:action]
|
|
49
|
+
}.compact
|
|
50
|
+
},
|
|
51
|
+
message: item[:footer_message]
|
|
52
|
+
}.to_json
|
|
53
|
+
end
|
|
54
|
+
%>
|
|
36
55
|
<button type="button"
|
|
37
56
|
class="<%= item_classes.join(' ') %>"
|
|
38
57
|
data-item-id="<%= item[:id] %>"
|
|
@@ -40,6 +59,7 @@
|
|
|
40
59
|
<% if item[:panel_id].present? %>data-panel-id="<%= item[:panel_id] %>"<% end %>
|
|
41
60
|
<% if item[:url].present? %>data-url="<%= item[:url] %>"<% end %>
|
|
42
61
|
<% if item[:title].present? %>title="<%= item[:title] %>"<% end %>
|
|
62
|
+
<% if submenu_footer_payload %>data-rmm-footer="<%= submenu_footer_payload %>"<% end %>
|
|
43
63
|
data-action="click->rmm-submenu#selectItem"
|
|
44
64
|
<% if item[:disabled] %>disabled<% end %>>
|
|
45
65
|
<% if item[:icon_svg].present? %>
|
data/config/importmap.rb
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
pin "rails_modal_manager", to: "rails_modal_manager/index.js"
|
|
7
7
|
pin "rails_modal_manager/modal_store", to: "rails_modal_manager/modal_store.js"
|
|
8
8
|
pin "rails_modal_manager/history_stack_manager", to: "rails_modal_manager/history_stack_manager.js"
|
|
9
|
+
pin "rails_modal_manager/footer_renderer", to: "rails_modal_manager/footer_renderer.js"
|
|
9
10
|
pin "rails_modal_manager/controllers/rmm_modal_controller", to: "rails_modal_manager/controllers/rmm_modal_controller.js"
|
|
10
11
|
pin "rails_modal_manager/controllers/rmm_overlay_controller", to: "rails_modal_manager/controllers/rmm_overlay_controller.js"
|
|
11
12
|
pin "rails_modal_manager/controllers/rmm_header_controller", to: "rails_modal_manager/controllers/rmm_header_controller.js"
|
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.
|
|
4
|
+
version: 1.0.50
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- reshacs
|
|
@@ -56,6 +56,7 @@ files:
|
|
|
56
56
|
- app/javascript/rails_modal_manager/controllers/rmm_sidebar_controller.js
|
|
57
57
|
- app/javascript/rails_modal_manager/controllers/rmm_submenu_controller.js
|
|
58
58
|
- app/javascript/rails_modal_manager/controllers/rmm_taskbar_controller.js
|
|
59
|
+
- app/javascript/rails_modal_manager/footer_renderer.js
|
|
59
60
|
- app/javascript/rails_modal_manager/history_stack_manager.js
|
|
60
61
|
- app/javascript/rails_modal_manager/index.js
|
|
61
62
|
- app/javascript/rails_modal_manager/modal_store.js
|