1/** 2 * renew.base.js â 2026 ì¬ì¬ì© UI ìì ¯ 3 * - custom-select ë± ë§í¬ì ê¸°ë° ê³µíµ ì»´í¬ëí¸ ì¤í¬ë¦½í¸ë¥¼ 모ìë¤. 4 * - ë ì´ìì/Lenis/í¤ë ë°íìì ui.common.js ì ì§. 5 */ 6 7/* ============================================================================= 8 * custom-select â ë¤ì´í°ë¸ select ì ì§ + button/list UI 9 * ============================================================================= */ 10(function (win, doc) { 11 'use strict'; 12 13 var OPEN_CLASS = 'custom-select--open'; 14 var PORTAL_ATTR = 'data-custom-select-portaled'; 15 var INIT_ATTR = 'data-custom-select-ready'; 16 17 function getList(wrap) { 18 var btn = wrap.querySelector('.custom-select__btn'); 19 var listId = btn && btn.getAttribute('aria-controls'); 20 if (listId) { 21 var byId = doc.getElementById(listId); 22 if (byId) return byId; 23 } 24 return wrap.querySelector('.custom-select__list'); 25 } 26 27 function getPortalRoot() { 28 return doc.getElementById('baseWrap') || doc.body; 29 } 30 31 function mountList(wrap, list) { 32 if (!wrap || !list || list.getAttribute(PORTAL_ATTR) === 'true') return; 33 list.setAttribute(PORTAL_ATTR, 'true'); 34 getPortalRoot().appendChild(list); 35 } 36 37 function unmountList(wrap, list) { 38 if (!wrap || !list || list.getAttribute(PORTAL_ATTR) !== 'true') return; 39 list.removeAttribute(PORTAL_ATTR); 40 wrap.appendChild(list); 41 } 42 43 function clearListPosition(list) { 44 if (!list) return; 45 list.style.top = ''; 46 list.style.left = ''; 47 list.style.width = ''; 48 } 49 50 function positionList(btn, list) { 51 if (!btn || !list) return; 52 var rect = btn.getBoundingClientRect(); 53 list.style.top = (rect.bottom + 4) + 'px'; 54 list.style.left = rect.left + 'px'; 55 list.style.width = rect.width + 'px'; 56 } 57 58 function closeSelect(wrap) { 59 if (!wrap) return; 60 var btn = wrap.querySelector('.custom-select__btn'); 61 var list = getList(wrap); 62 wrap.classList.remove(OPEN_CLASS); 63 if (btn) btn.setAttribute('aria-expanded', 'false'); 64 if (list) { 65 list.hidden = true; 66 clearListPosition(list); 67 unmountList(wrap, list); 68 } 69 } 70 71 function closeAll(except) { 72 doc.querySelectorAll('.custom-select.' + OPEN_CLASS).forEach(function (wrap) { 73 if (except && wrap === except) return; 74 closeSelect(wrap); 75 }); 76 } 77 78 function syncUiFromNative(wrap) { 79 var native = wrap.querySelector('.custom-select__native'); 80 var btn = wrap.querySelector('.custom-select__btn'); 81 var list = getList(wrap); 82 if (!native || !btn || !list) return; 83 84 var selected = native.options[native.selectedIndex]; 85 var value = native.value; 86 var label = selected ? selected.text : ''; 87 var isPlaceholder = !value; 88 var placeholder = wrap.getAttribute('data-placeholder') || 'ì íí´ ì£¼ì¸ì'; 89 90 btn.textContent = isPlaceholder ? placeholder : (label || placeholder); 91 btn.classList.toggle('is-placeholder', isPlaceholder); 92 93 list.querySelectorAll('.custom-select__item').forEach(function (item) { 94 var itemBtn = item.querySelector('button'); 95 var isActive = !!(itemBtn && itemBtn.getAttribute('data-value') === value); 96 item.classList.toggle('custom-select__item--active', isActive); 97 if (itemBtn) { 98 if (isActive) itemBtn.setAttribute('title', 'ì íë¨'); 99 else itemBtn.removeAttribute('title'); 100 } 101 }); 102 } 103 104 function setNativeValue(wrap, value) { 105 var native = wrap.querySelector('.custom-select__native'); 106 if (!native) return; 107 native.value = value; 108 try { 109 native.dispatchEvent(new Event('change', { bubbles: true })); 110 } catch (e) { 111 var evt = doc.createEvent('HTMLEvents'); 112 evt.initEvent('change', true, false); 113 native.dispatchEvent(evt); 114 } 115 syncUiFromNative(wrap); 116 } 117 118 function buildListFromOptions(wrap, native, listId) { 119 var list = doc.createElement('ul'); 120 list.id = listId; 121 list.className = 'custom-select__list'; 122 list.hidden = true; 123 list.setAttribute('role', 'listbox'); 124 125 Array.prototype.forEach.call(native.options, function (opt) { 126 var li = doc.createElement('li'); 127 li.className = 'custom-select__item'; 128 li.setAttribute('role', 'presentation'); 129 130 var itemBtn = doc.createElement('button'); 131 itemBtn.type = 'button'; 132 itemBtn.setAttribute('role', 'option'); 133 itemBtn.setAttribute('data-value', opt.value); 134 itemBtn.textContent = opt.text; 135 136 if (opt.disabled) { 137 itemBtn.disabled = true; 138 } 139 140 li.appendChild(itemBtn); 141 list.appendChild(li); 142 }); 143 144 wrap.appendChild(list); 145 return list; 146 } 147 148 function ensureUi(wrap) { 149 var native = wrap.querySelector('select'); 150 if (!native) return null; 151 152 native.classList.add('custom-select__native'); 153
154 var id = native.id || ('custom-select-' + Math.random().toString(36).slice(2, 9)); 155 if (!native.id) native.id = id; 156 157 var listId = id + '-list'; 158 var btn = wrap.querySelector('.custom-select__btn'); 159 var list = wrap.querySelector('.custom-select__list'); 160 161 if (!btn) { 162 btn = doc.createElement('button'); 163 btn.type = 'button'; 164 btn.className = 'custom-select__btn'; 165 btn.setAttribute('aria-haspopup', 'listbox'); 166 btn.setAttribute('aria-expanded', 'false'); 167 btn.setAttribute('aria-controls', listId); 168 wrap.appendChild(btn); 169 } else { 170 btn.setAttribute('aria-controls', listId); 171 } 172 173 if (!list) { 174 list = buildListFromOptions(wrap, native, listId); 175 } else if (!list.id) { 176 list.id = listId; 177 } 178 179 syncUiFromNative(wrap); 180 return { native: native, btn: btn, list: list }; 181 } 182 183 function bindWrap(wrap) { 184 if (wrap.getAttribute(INIT_ATTR) === 'true') return; 185 var ui = ensureUi(wrap); 186 if (!ui) return; 187 188 var btn = ui.btn; 189 var list = ui.list; 190 191 btn.addEventListener('click', function () { 192 var isOpen = wrap.classList.contains(OPEN_CLASS); 193 closeAll(); 194 if (isOpen) return; 195 196 wrap.classList.add(OPEN_CLASS); 197 btn.setAttribute('aria-expanded', 'true'); 198 list.hidden = false; 199 mountList(wrap, list); 200 positionList(btn, list); 201 }); 202 203 list.addEventListener('click', function (e) { 204 var optionBtn = e.target.closest('.custom-select__item > button'); 205 if (!optionBtn || optionBtn.disabled) return; 206 setNativeValue(wrap, optionBtn.getAttribute('data-value') || ''); 207 closeSelect(wrap); 208 btn.focus(); 209 }); 210 211 wrap.setAttribute(INIT_ATTR, 'true'); 212 } 213 214 function repositionOpen() { 215 doc.querySelectorAll('.custom-select.' + OPEN_CLASS).forEach(function (wrap) { 216 var btn = wrap.querySelector('.custom-select__btn'); 217 var list = getList(wrap); 218 positionList(btn, list); 219 }); 220 } 221 222 var scrollListenersBound = false; 223 var lenisListenerBound = false; 224 225 function bindLenisScrollIfNeeded() { 226 if (lenisListenerBound) return; 227 if (win.__lenis && typeof win.__lenis.on === 'function') { 228 lenisListenerBound = true; 229 win.__lenis.on('scroll', repositionOpen); 230 } 231 } 232 233 function bindCustomSelectScrollListeners() { 234 if (!scrollListenersBound) { 235 scrollListenersBound = true; 236 win.addEventListener('resize', repositionOpen); 237 238 var baseWrap = doc.getElementById('baseWrap'); 239 if (baseWrap) { 240 baseWrap.addEventListener('scroll', repositionOpen, { passive: true }); 241 } 242 win.addEventListener('scroll', repositionOpen, { passive: true }); 243 } 244 245 bindLenisScrollIfNeeded(); 246 if (!lenisListenerBound) { 247 setTimeout(bindLenisScrollIfNeeded, 0); 248 } 249 } 250 251 function initCustomSelect2026(root) { 252 var scope = root && root.querySelectorAll ? root : doc; 253 scope.querySelectorAll('.custom-select').forEach(bindWrap); 254 } 255 256 doc.addEventListener('click', function (e) { 257 if (e.target.closest('.custom-select') || e.target.closest('.custom-select__list')) return; 258 closeAll(); 259 }); 260 261 doc.addEventListener('keydown', function (e) { 262 if (e.key === 'Escape' || e.keyCode === 27) { 263 closeAll(); 264 } 265 }); 266 267 function onCustomSelectReady() { 268 initCustomSelect2026(); 269 bindCustomSelectScrollListeners(); 270 } 271 272 if (doc.readyState === 'loading') { 273 doc.addEventListener('DOMContentLoaded', onCustomSelectReady); 274 } else { 275 onCustomSelectReady(); 276 } 277 278 win.initCustomSelect2026 = initCustomSelect2026; 279})(window, document);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.