1import { delegateEvent } from "./tools.js?vjs=1"; 2 3/** 4 * @param {HTMLInputElement} inputElement 5 * @param {HTMLButtonElement} buttonElement 6 */ 7function togglePassword(inputElement, buttonElement) { 8 const isPasswordVisible = inputElement.getAttribute("type") === "text"; 9 10 if (isPasswordVisible) { 11 inputElement.setAttribute("type", "password"); 12 buttonElement.setAttribute("aria-pressed", "false"); 13 buttonElement.setAttribute("aria-label", buttonElement.dataset.ariaLabelShow); 14 } else { 15 inputElement.setAttribute("type", "text"); 16 buttonElement.setAttribute("aria-pressed", "true"); 17 buttonElement.setAttribute("aria-label", buttonElement.dataset.ariaLabelHide); 18 } 19} 20 21/** 22 * @param rootElement HTMLElement 23 * @depricated pÅeklep v názvu tÅÃdy .js-toogle-password 24 */ 25export function initTooglePassword(rootElement) { 26 rootElement.addEventListener( 27 "click", 28 delegateEvent(".js-toogle-password", (event) => { 29 togglePassword( 30 event.delegateTarget.closest(".form-input").querySelector("input"), 31 ); 32 }), 33 ); 34} 35 36export function initTogglePassword(rootElement) { 37 // TODO až budou odstranÄny vÅ¡echny elememty s pÅeklepem možno odstranit 38 initTooglePassword(rootElement); 39 40 rootElement.addEventListener( 41 "click", 42 delegateEvent(".js-toggle-password", (event) => { 43 const button = event.delegateTarget; 44 const input = button.closest(".form-input").querySelector("input"); 45 togglePassword(input, button); 46 }), 47 ); 48} 49 50 51/** 52 * @param rootElement HTMLElement 53 */ 54export function initCheckboxToggle(rootElement) { 55 rootElement.querySelectorAll(".js-checkbox-toggle").forEach((element) => { 56 element.addEventListener("change", (event) => { 57 const selector = event.target.dataset.targetSelector; 58 rootElement.querySelector(selector).classList.toggle("is-hidden"); 59 }); 60 }); 61} 62 63/** 64 * @param rootElement HTMLElement 65 */ 66export function initCheckboxRequiredToggle(rootElement) { 67 rootElement 68 .querySelectorAll(".js-checkbox-required-toggle") 69 .forEach((element) => { 70 element.addEventListener("change", (event) => { 71 const selector = event.target.dataset.requiredSelector; 72 73 rootElement.querySelectorAll(selector).forEach((targetElement) => { 74 if (event.target.checked) { 75 targetElement.setAttribute("required", ""); 76 } else { 77 targetElement.removeAttribute("required"); 78 } 79 }); 80 }); 81 }); 82} 83 84/** 85 * @param rootElement HTMLElement 86 */ 87export function initRadioRequiredToggle(rootElement) { 88 rootElement 89 .querySelectorAll(".js-radio-required-toggle") 90 .forEach((element) => { 91 element.addEventListener("change", (event) => { 92 const radioElement = event.target; 93 const name = radioElement.getAttribute("name"); 94 radioElement 95 .closest("form") 96 .querySelectorAll("input[name=" + name + "]") 97 .forEach((element) => { 98 const selector = element.dataset.requiredSelector; 99 const isCurrent = 100 selector === event.target.dataset.requiredSelector; 101 102 rootElement = document.querySelector("body"); 103 104 rootElement.querySelectorAll(selector).forEach((targetElement) => { 105 if (event.target.checked && isCurrent) { 106 targetElement.setAttribute("required", ""); 107 } else { 108 targetElement.removeAttribute("required"); 109 } 110 }); 111 }); 112 }); 113 }); 114} 115 116/** 117 * @param rootElement HTMLElement 118 */ 119export function initRadioToggle(rootElement) { 120 rootElement.querySelectorAll(".js-radio-toggle").forEach((element) => { 121 element.addEventListener("change", (event) => { 122 const radioElement = event.target; 123 const name = radioElement.getAttribute("name"); 124 radioElement 125 .closest("form") 126 .querySelectorAll("input[name=" + name + "]") 127 .forEach((element) => { 128 const selector = element.dataset.targetSelector; 129 130 rootElement.querySelectorAll(selector).forEach((targetElement) => { 131 targetElement.classList.toggle("is-hidden", !element.checked); 132 }); 133 }); 134 }); 135 }); 136} 137 138/** 139 * @param parentElement HTMLElement 140 */ 141export function initializeForms(parentElement) { 142 if (parentElement === undefined) { 143 return; 144 } 145 146 initCheckboxToggle(parentElement); 147 initTogglePassword(parentElement); 148 initRadioToggle(parentElement); 149 initCheckboxRequiredToggle(parentElement); 150 initRadioRequiredToggle(parentElement); 151} 152 153export class InputQuantity extends HTMLElement { 154 static formAssociated = true; 155 #internals; 156 #shadowRoot; 157 static observedAttributes = ["value", "min", "max", "step", "name", "label", "data-aria-labelledby-id"]; 158 159 constructor() { 160 super(); 161 this.#internals = this.attachInternals(); 162 this.#shadowRoot = this.attachShadow({ 163 mode: "open", 164 delegatesFocus: true, 165 }); 166 167 let wrapper = document.createElement("div"); 168 wrapper.classList.add("wrapper"); 169 const inputId = `quantity_${Math.random().toString(36).substring(2, 9)}`; 170 wrapper.innerHTML = ` 171 <button data-action="down" aria-label="SnÞit množstvÃ"> 172 <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> 173 <path fill-rule="evenodd" clip-rule="evenodd" d="M6 11.25h12v1.5
173H6v-1.5Z" /> 174 </svg> 175 </button> 176 <input id="${inputId}" type="number" value="1" min="1" step="1" aria-label="množstvÃ" /> 177 <button data-action="up" aria-label="Zvýšit množstvÃ"> 178 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" aria-hidden="true"> 179 <path fill-rule="evenodd" clip-rule="evenodd" d="M11.25 12.75V18H12.75V12.75L18 12.75V11.25L12.75 11.25V6H11.25V11.25L6 11.25V12.75L11.25 12.75Z"/> 180 </svg> 181 </button> 182 <dialog> 183 <div>Text</div> 184 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="8" aria-hidden="true"> 185 <path d="M7 8L13.9282 0.5H0.0717969L7 8Z" fill="#4D4D4D"/> 186 </svg> 187 </dialog> 188 `; 189 190 let style = document.createElement("style"); 191 style.innerHTML = ` 192 * { 193 box-sizing: border-box; 194 } 195 .wrapper { 196 display: flex; 197 justify-content: space-between; 198 border-radius: 6px; 199 border: 1px solid var(--color-gray-scale-gray-100); 200 background: #fff; 201 height: 46px; 202 width: 108px; 203 position: relative; 204 } 205 206 button { 207 display: flex; 208 align-items: center; 209 justify-content: center; 210 flex-shrink: 0; 211 padding: 0; 212 border: 0; 213 width: 34px; 214 height: 45px; 215 background: var(--color-gray-scale-gray-50); 216 transition: all 300ms; 217 cursor: pointer; 218 } 219 220 button svg { 221 fill: currentColor; 222 pointer-events: none; 223 } 224 225 button:hover { 226 background: var(--color-branding-orange); 227 color: #fff; 228 } 229 230 @media (max-width: 600px) { 231 .wrapper { 232 height: 38px; 233 } 234 button { 235 height: 36px; 236 } 237 238 } 239 240 [data-action="down"] { 241 border-radius: 5px 0 0 5px; 242 } 243 244 [data-action="up"] { 245 border-radius: 0 5px 5px 0; 246 } 247 248 input { 249 width: 38px; 250 border: none; 251 appearance: none; 252 -moz-appearance: textfield; 253 text-align: center; 254 outline: none; 255 padding: 0; 256 } 257 258 input::inner-spin-button { 259 apperance: none; 260 } 261 input::-webkit-inner-spin-button { 262 -webkit-appearance: none; 263 } 264 input::-webkit-outer-spin-button { 265 -webkit-appearance: none; 266 } 267 268 dialog { 269 min-width: 130px; 270 margin-bottom: 10px; 271 color: #fff; 272 bottom: calc(100% - 4px); 273 font-size: 12px; 274 font-weight: 600; 275 line-height: 129%; 276 border-radius: 5px; 277 background: var(--color-gray-scale-gray-700, #4D4D4D); 278 padding: 8px 279 } 280 281 dialog svg { 282 position: absolute; 283 top: 100%; 284 margin-top: -1px; 285 left: 46px; 286 } 287 `; 288 289 this.#shadowRoot.append(wrapper, style); 290 291 let input = this.#shadowRoot.querySelector("input"); 292 input.addEventListener("change", () => { 293 this.#internals.setFormValue(input.value); 294 this.setAttribute("value", input.value); 295 this.#validate(input.value); 296 297 this.dispatchEvent( 298 new CustomEvent("change", { 299 bubbles: true, 300 detail: { value: input.value }, 301 }), 302 ); 303 }); 304 305 input.addEventListener("input", () => { 306 if (parseInt(input.value) < parseInt(input.min)) { 307 input.value = input.min; 308 } 309 310 if (parseInt(input.value) > parseInt(input.max)) { 311 input.value = input.max; 312 } 313 }); 314 315 this.#shadowRoot.querySelectorAll("button").forEach((button) => { 316 button.addEventListener("click", (event) => { 317 if (event.target.dataset.action === "down") { 318 this.stepDown(); 319 } else { 320 this.stepUp(); 321 } 322 let input = this.#shadowRoot.querySelector("input"); 323 let newValue = input.value; 324 this.setAttribute("value", newValue); 325 this.#validate(newValue); 326 327 this.dispatchEvent( 328 new CustomEvent("change", { 329 bubbles: true, 330 detail: { value: newValue }, 331 }), 332 ); 333 }); 334 }); 335 } 336 337 static register() { 338 if (customElements.get("input-quantity") !== undefined) { 339 return; 340 } 341 342 customElements.define("input-quantity", this); 343 } 344 345 #validate(newValue) { 346 let input = this.#shadowRoot.querySelector("input"); 347 if (parseInt(newValue) >= parseInt(input.getAttribute("max"))) { 348 this.#showDialog(); 349 } else { 350 this.#hideDialog(); 351 } 352 } 353 354 formStateRestoreCallback(state, reason) { 355 this.#shadowRoot.querySelector("input").value = state; 356 } 357 358 attributeChangedCallback(name, oldValue, newValue) { 359 let input = this.#shadowRoot.querySelector("input"); 360 361 switch (name) { 362 case "value": 363 this.#internals.setFormValue(newValue); 364 input.setAttribute(name, newValue); 365 this.#validate(newValue); 366 break; 367 case "label": 368 case "data-aria-labelledby-id": 369 this.#updateAccessibleName(); 370 break; 371 default: 372 input.setAttribute(name, newValue); 373 } 374 } 375 376 #updateAccessibleName() { 377 const input = this.#shadowRoot.querySelector("input"); 378 const label = this.getAttribute("label") || "množstvÃ"; 379 const labelledbyId = this.dataset.ariaLabelledbyId; 380 381 input.setAttribute("aria-label", label); 382 383 if (labelledbyId) { 384 const inputId = input.getAttribute("id"); 385 input.setAttribute("aria-labelledby", `${inputId} ${labelledbyId}`); 386 } else { 387 input.removeAttribute("aria-labelledby"); 388 } 389 } 390 391 stepDown(n) { 392 this.#shadowRoot.querySelector("input").stepDown(n); 393 } 394 395 stepUp(n) { 396 this.#shadowRoot.querySelector("input").stepUp(n); 397 } 398 399 checkValidity() { 400 this.#shadowRoot.querySelector("input").checkValidity(); 401 } 402 403 reportValidity() { 404 this.#shadowRoot.querySelector("input").reportValidity(); 405 } 406 407 connectedCallback() { 408 this.#updateAccessibleName(); 409 } 410 411 #showDialog() { 412 let maxValue = this.#shadowRoot.querySelector("input").getAttribute("max"); 413 let message = this.dataset.message 414 ? this.dataset.message.replace("@pocet", maxValue) 415 : `Maximum je ${maxValue}`; 416 417 let dialog = this.#shadowRoot.querySelector("dialog"); 418 dialog.querySelector("div").innerHTML = message; 419 dialog.show(); 420 setTimeout(() => this.#hideDialog(), 3000); 421 } 422 423 #hideDialog() { 424 this.#shadowRoot.querySelector("dialog").close(); 425 } 426}
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.