1import { cloneTemplate } from '/assets/scripts/utilities/index.js'; 2import { common } from '/assets/scripts/core/core.js'; 3 4const config = { 5 // selector 6 buttonTemplateSelector: '.js-showMoreOnSp__buttonTemplate', 7 buttonWrapSelector: '.js-showMoreOnSp__buttonWrap', 8 buttonSelector: '.js-showMoreOnSp__button', 9 contentSelector: '.js-showMoreOnSp__content', 10 11 // class name 12 hideStateClassName: 'js-showMoreOnSp__content--hide', 13 14 // text 15 defaultButtonText: { 16 show: 'ãã£ã¨è¦ã', 17 hide: 'éãã', 18 }, 19}; 20 21export const setShowMoreOnSp = (element) => { 22 /** @type {HTMLElement} æ©è½ã®èµ·ç¹ã¨ãªãè¦ç´ */ 23 const root = element; 24 /** @type {HTMLElement} */ 25 const content = root.querySelector(config.contentSelector); 26 /** @type {HTMLTemplateElement} ééãã¿ã³ãå«ãtemplateè¦ç´ */ 27 const buttonTemplate = root.querySelector(config.buttonTemplateSelector); 28 /** @type {HTMLElement} ééãã¿ã³ã®ã©ããã¼ */ 29 let buttonWrap = null; 30 /** @type {HTMLElement} ééãã¿ã³ */ 31 let button = null; 32 /** @type {number} æãããã¿ã®é¾å¤ */ 33 const threshold = Number(root.dataset.showmoreThreshold) || 100; 34 /** @type {boolean} ééã®ç¶æ ãã©ã° */ 35 let isPanelShow = root.dataset.showmoreShow === 'true'; 36 /** @type {{ show: string, hide: string }} ééãã¿ã³ã®ããã¹ã */ 37 let buttonText = { 38 show: '', 39 hide: '', 40 }; 41 let contentHeight = null; 42 43 /** 44 * ã«ã¹ã¿ã ããããã£ãã»ãã 45 */ 46 const setCustomProperty = () => { 47 content.style.setProperty('--max-content-height', '1fr'); 48 content.style.setProperty('--min-content-height', `${threshold / contentHeight}fr`); 49 }; 50 51 /** 52 * ããªã¬ã¼ã使 53 */ 54 const buildButton = () => { 55 cloneTemplate(buttonTemplate, content); 56 buttonWrap = root.querySelector(config.buttonWrapSelector); 57 button = root.querySelector(config.buttonSelector); 58 59 // åæç¶æ ã«å¿ãã¦ééãã¿ã³ã®ããã¹ããã»ãã 60 buttonText = { 61 show: button.dataset.showmoreShowbuttontext && button.dataset.showmoreShowbuttontext.trim() !== '' ? button.dataset.showmoreShowbuttontext : config.defaultButtonText.show, 62 hide: button.dataset.showmoreHidebuttontext && button.dataset.showmoreHidebuttontext.trim() !== '' ? button.dataset.showmoreHidebuttontext : config.defaultButtonText.hide, 63 }; 64 65 if (isPanelShow) { 66 button.innerText = buttonText.hide; 67 } else { 68 button.innerText = buttonText.show; 69 } 70 }; 71 72 const build = () => { 73 buildButton(); 74 75 // åæç¶æ ã«å¿ãã¦ééã®è¦ãç®ãã»ãã 76 if (isPanelShow) { 77 content.classList.remove(config.hideStateClassName); 78 } else { 79 content.classList.add(config.hideStateClassName); 80 } 81 }; 82 83 /** 84 * é表示é¨åã«ãã©ã¼ã«ã¹å¶éãé©ç¨ 85 */ 86 const disableHiddenFocus = () => { 87 const focusableElements = content.querySelectorAll(common.focusableSelectors.join(',')); 88 89 for(const focusable of focusableElements) { 90 const rect = focusable.getBoundingClientRect(); 91 const contentRect = content.getBoundingClientRect(); 92 const relativeTop = rect.top - contentRect.top; 93 94 // threshold ãè¶ ãã¦ããè¦ç´ ã ã tabindex ãç¡å¹ã« 95 if (relativeTop > threshold) { 96 focusable.dataset.prevTabindex = focusable.getAttribute('tabindex') ?? ''; 97 focusable.setAttribute('tabindex', '-1'); 98 focusable.setAttribute('aria-hidden', 'true'); 99 } 100 } 101 }; 102 103 /** 104 * é表示å¶éãè§£é¤ï¼éããã¨ãï¼ 105 */ 106 const enableAllFocus = () => { 107 const hiddenElements = content.querySelectorAll('[aria-hidden="true"]'); 108 109 for(const hidden of hiddenElements) { 110 const { prevTabindex } = hidden.dataset; 111 112 if (prevTabindex === '') { 113 hidden.removeAttribute('tabindex'); 114 } else { 115 hidden.setAttribute('tabindex', prevTabindex); 116 } 117 118 hidden.removeAttribute('aria-hidden'); 119 delete hidden.dataset.prevTabindex; 120 } 121 }; 122 123 /** 124 * éããç¶æ ã§è¦ãã¦ããªãè¦ç´ ã«ãã©ã¼ã«ã¹ãç§»å 125 */ 126 const focusFirstHiddenElement = () => { 127 const focusableElements = content.querySelectorAll(common.focusableSelectors); 128 let firstHiddenElement = null; 129 130 for(const focusable of focusableElements) { 131 const rect = focusable.getBoundingClientRect(); 132 const contentRect = content.getBoundingClientRect(); 133 const relativeTop = rect.top - contentRect.top; 134 135 // thresholdããä¸ã«ããè¦ç´ ã¯ãã©ã¼ã«ã¹å¯¾è±¡ã¨ããªã 136 if (relativeTop > threshold && !firstHiddenElement) { 137 firstHiddenElement = focusable;
137 // è¦ãã¦ããªãæåã®è¦ç´ 138 } 139 } 140 141 // è¦ãã¦ããªãã£ãè¦ç´ ã«ãã©ã¼ã«ã¹ãç§»å 142 if (firstHiddenElement) { 143 firstHiddenElement.focus(); 144 } 145 }; 146 147 /** 148 * 表示ãã 149 */ 150 const showPanel = () => { 151 content.classList.remove(config.hideStateClassName); 152 enableAllFocus(); 153 isPanelShow = true; 154 button.innerText = buttonText.hide; 155 156 content.addEventListener('transitionend', focusFirstHiddenElement, { 157 once: true, 158 }); 159 }; 160 161 /** 162 * é表示ã«ãã 163 */ 164 const hidePanel = () => { 165 content.classList.add(config.hideStateClassName); 166 disableHiddenFocus(); 167 isPanelShow = false; 168 button.innerText = buttonText.show; 169 }; 170 171 /** 172 * ã¤ãã³ãç»é² 173 */ 174 const bindEvent = () => { 175 button.addEventListener('click', (event) => { 176 event.preventDefault(); 177 178 if (isPanelShow) { 179 hidePanel(); 180 } else { 181 showPanel(); 182 } 183 }); 184 }; 185 186 /** 187 * æ©è½åé¤ 188 */ 189 const destroy = () => { 190 if (!content) { 191 return; 192 } 193 194 const focusableElements = content.querySelectorAll(common.focusableSelectors.join(',')); 195 196 for(const focusable of focusableElements) { 197 focusable.removeAttribute('tabindex'); 198 focusable.removeAttribute('aria-hidden'); 199 } 200 201 content.style.removeProperty('--max-content-height'); 202 content.style.removeProperty('--min-content-height'); 203 content.classList.remove(config.hideStateClassName); 204 205 if (buttonWrap) { 206 buttonWrap.remove(); 207 } 208 } 209 210 /** 211 * ã¡ãã£ã¢ã¯ã¨ãªã®ç£è¦ 212 */ 213 const onMatch = () => { 214 common.mediaQuery.addEventListener('change', (event) => { 215 destroy(); 216 217 if (!event.matches) { 218 contentHeight = content.offsetHeight; 219 220 if (contentHeight < threshold) { 221 return; 222 } 223 224 setCustomProperty(); 225 build(); 226 bindEvent(); 227 disableHiddenFocus(); 228 } 229 }); 230 }; 231 232 onMatch(); 233 234 if (!common.mediaQuery.matches) { 235 contentHeight = content.offsetHeight; 236 237 if (contentHeight < threshold) { 238 return; 239 } 240 241 setCustomProperty(); 242 build(); 243 bindEvent(); 244 disableHiddenFocus(); 245 } 246};
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.