PageSourceSearch

https://ardene.com/cdn/shop/t/239/assets/snippet.sort-by-dropdown.js?v=35690426143889497741790728944

js ardene.com collected 2026-10-02 04:08:56 UTC 16,924 bytes, 2 lines download raw bytes

1(function(){"use strict";var __webpack_modules__={
1"./src/snippets/snippet.sort-by-dropdown.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _sort_by_dropdown_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sort-by-dropdown.css */ \"./src/snippets/sort-by-dropdown.css\");\n\n\nconst ATTR = {\n  SORT_DROPDOWN_TRIGGER: 'data-sort-dropdown-trigger',\n  SORT_DROPDOWN: 'data-sort-dropdown',\n  SORT_OPTION: 'data-sort-option',\n  SORT_CURRENT: 'data-sort-current',\n  ARIA_EXPANDED: 'aria-expanded',\n  ARIA_SELECTED: 'aria-selected',\n  ARIA_LABEL: 'aria-label',\n  TABINDEX: 'tabindex',\n  HIDDEN: 'hidden',\n};\n\nconst SELECTORS = {\n  SORT_DROPDOWN_TRIGGER: '[data-sort-dropdown-trigger]',\n  SORT_DROPDOWN: '[data-sort-dropdown]',\n  SORT_OPTIONS: '[data-sort-option]',\n  SORT_CURRENT: '[data-sort-current]',\n};\n\nconst CLASSES = {\n  ACTIVE: 'active',\n  HAS_SELECTION: 'sortby-dropdown--has-selection',\n};\n\nconst KEYS = {\n  ENTER: 'Enter',\n  SPACE: ' ',\n  ARROW_DOWN: 'ArrowDown',\n  ARROW_UP: 'ArrowUp',\n  ESCAPE: 'Escape',\n  TAB: 'Tab',\n};\n\nconst TIMING = {\n  PROCESSING_DELAY: 1000,\n  URL_UPDATE_DELAY: 10,\n};\n\nconst DEFAULTS = {\n  SORT_VALUE: 'created-descending',\n  TABINDEX_ACTIVE: '0',\n  TABINDEX_INACTIVE: '-1',\n};\n\nconst STYLES = {\n  LOADING_OPACITY: '0.6',\n  NORMAL_OPACITY: '1',\n  POINTER_EVENTS_NONE: 'none',\n  POINTER_EVENTS_AUTO: 'auto',\n};\n\nconst URL_PARAMS = {\n  SORT_BY: 'sort_by',\n  PAGE: 'page',\n};\n\n/**\n * SortbyDropdown Component\n *\n * Handles sort dropdown functionality including:\n * - Dropdown open/close behavior\n * - Sort option selection and URL updates\n * - Keyboard navigation and accessibility\n * - Loading states and visual feedback\n */\nclass SortbyDropdownComponent extends HTMLElement {\n  constructor() {\n    super();\n    this.sortDropdownTrigger = this.querySelector(SELECTORS.SORT_DROPDOWN_TRIGGER);\n    this.sortDropdown = this.querySelector(SELECTORS.SORT_DROPDOWN);\n    this.sortOptions = this.querySelectorAll(SELECTORS.SORT_OPTIONS);\n    this.sortCurrent = this.querySelector(SELECTORS.SORT_CURRENT);\n    this.currentSortValue = null;\n    this.isProcessing = false;\n  }\n\n  /**\n   * Called when the component is connected to the DOM\n   * @public\n   */\n  connectedCallback() {\n    this.init();\n    this.setupEventListeners();\n  }\n\n  /**\n   * Called when the component is disconnected from the DOM\n   * @public\n   */\n  disconnectedCallback() {\n    this.cleanup();\n  }\n\n  /**\n   * Initialize the sort dropdown component\n   * @private\n   */\n  init() {\n    // Load saved sort preference from URL\n    this.loadSavedSort();\n  }\n\n  /**\n   * Set up all event listeners for the sort dropdown\n   * @private\n   */\n  setupEventListeners() {\n    // Bind methods to preserve context and store references for cleanup\n    this.boundToggleSortDropdown = this.toggleSortDropdown.bind(this);\n    this.boundHandleTriggerKeydown = this.handleTriggerKeydown.bind(this);\n    this.boundHandleSortChange = this.handleSortChange.bind(this);\n    this.boundHandleOptionKeydown = this.handleOptionKeydown.bind(this);\n    this.boundHandleOutsideClick = this.handleOutsideClick.bind(this);\n    this.boundHandleDocumentKeydown = this.handleDocumentKeydown.bind(this);\n\n    // Trigger button events\n    if (this.sortDropdownTrigger) {\n      this.sortDropdownTrigger.addEventListener('click', this.boundToggleSortDropdown);\n      this.sortDropdownTrigger.addEventListener('keydown', this.boundHandleTriggerKeydown);\n    }\n\n    // Option events\n    this.sortOptions.forEach((option) => {\n      option.addEventListener('click', this.boundHandleSortChange);\n      option.addEventListener('keydown', this.boundHandleOptionKeydown);\n    });\n\n    // Document events for outside clicks and escape key\n    document.addEventListener('click', this.boundHandleOutsideClick);\n    document.addEventListener('keydown', this.boundHandleDocumentKeydown);\n  }\n\n  /**\n   * Clean up event listeners when component is removed\n   * @private\n   */\n  cleanup() {\
1n    if (this.sortDropdownTrigger && this.boundToggleSortDropdown) {\n      this.sortDropdownTrigger.removeEventListener('click', this.boundToggleSortDropdown);\n      this.sortDropdownTrigger.removeEventListener('keydown', this.boundHandleTriggerKeydown);\n    }\n\n    if (this.boundHandleSortChange && this.boundHandleOptionKeydown) {\n      this.sortOptions.forEach((option) => {\n        option.removeEventListener('click', this.boundHandleSortChange);\n        option.removeEventListener('keydown', this.boundHandleOptionKeydown);\n      });\n    }\n\n    if (this.boundHandleOutsideClick) {\n      document.removeEventListener('click', this.boundHandleOutsideClick);\n    }\n    if (this.boundHandleDocumentKeydown) {\n      document.removeEventListener('keydown', this.boundHandleDocumentKeydown);\n    }\n  }\n\n  /**\n   * Toggle the sort dropdown open/closed state\n   * @private\n   * @param {Event} event - Click event\n   */\n  toggleSortDropdown(event) {\n    event.preventDefault();\n    event.stopPropagation();\n\n    const isExpanded = this.sortDropdownTrigger.getAttribute(ATTR.ARIA_EXPANDED) === 'true';\n\n    if (isExpanded) {\n      this.closeSortDropdown();\n    } else {\n      this.openSortDropdown();\n    }\n  }\n\n  /**\n   * Open the sort dropdown\n   * @private\n   */\n  openSortDropdown() {\n    this.sortDropdownTrigger.setAttribute(ATTR.ARIA_EXPANDED, 'true');\n    this.sortDropdown.removeAttribute(ATTR.HIDDEN);\n  }\n\n  /**\n   * Close the sort dropdown\n   * @private\n   */\n  closeSortDropdown() {\n    this.sortDropdownTrigger.setAttribute(ATTR.ARIA_EXPANDED, 'false');\n    this.sortDropdown.setAttribute(ATTR.HIDDEN, '');\n  }\n\n  /**\n   * Handle clicks outside the dropdown to close it\n   * @private\n   * @param {Event} event - Click event\n   */\n  handleOutsideClick(event) {\n    if (!this.contains(event.target)) {\n      this.closeSortDropdown();\n    }\n  }\n\n  /**\n   * Handle sort option selection\n   * @private\n   * @param {Event} event - Click event on sort option\n   */\n  handleSortChange(event) {\n    // Prevent multiple clicks while processing\n    if (this.isProcessing) {\n      event.preventDefault();\n      return;\n    }\n\n    const option = event.currentTarget;\n    const sortValue = option?.getAttribute(ATTR.SORT_OPTION);\n    const sortText = option?.textContent?.trim();\n\n    // Validate that we have the required data\n    if (!sortValue || !sortText) {\n      console.warn('Invalid sort option clicked:', option);\n      return;\n    }\n\n    // Always apply sort, even if it's the same value (to handle initial state issues)\n    this.isProcessing = true;\n    this.setLoadingState(true);\n    this.setSortOption(sortValue, sortText, true);\n    this.applySortToURL(sortValue);\n\n    // Reset processing flag after a short delay to prevent accidental double-clicks\n    setTimeout(() => {\n      this.isProcessing = false;\n      this.setLoadingState(false);\n    }, TIMING.PROCESSING_DELAY);\n\n    this.closeSortDropdown();\n  }\n\n  /**\n   * Set loading state visual feedback\n   * @private\n   * @param {boolean} isLoading - Whether component is in loading state\n   */\n  setLoadingState(isLoading) {\n    if (this.sortDropdownTrigger) {\n      this.sortDropdownTrigger.style.opacity = isLoading\n        ? STYLES.LOADING_OPACITY\n        : STYLES.NORMAL_OPACITY;\n      this.sortDropdownTrigger.style.pointerEvents = isLoading\n        ? STYLES.POINTER_EVENTS_NONE\n        : STYLES.POINTER_EVENTS_AUTO;\n    }\n\n    this.sortOptions.forEach((option) => {\n      option.style.opacity = isLoading ? STYLES.LOADING_OPACITY : STYLES.NORMAL_OPACITY;\n      option.style.pointerEvents = isLoading\n        ? STYLES.POINTER_EVENTS_NONE\n        : STYLES.POINTER_EVENTS_AUTO;\n    });\n  }\n\n  /**\n   * Set the active sort option and update UI state\n   * @private\n   * @param {string} value - Sort value\n   * @param {string} text - Display text for sort option\n   * @param {boolean} showText - Whether to show the selection text\n   */\n  setSortOption(value, text, showText = true) {\n    this.currentSortValue = value;\n\n    // Determine if we should show selection state - show text when explicitly selected\n    const hasSelection = showText;\n\n    // Update CSS class for visual state\n    if (hasSelection) {\n      this.classList.add(CLASSES.HAS_SELECTION);\n    } else {\n      this.classList.remove(CLASSES.HAS_SELECTION);\n    }\n\n    // Update current sort display - show text when explicitly selected\n    if (this.sortCurrent) {\n      if (hasSelection) {\n        this.sortCurrent.textContent = text;\n      } else {\n        this.sortCurrent.textContent = '';\n      }\n    }\n\n    // Update aria-label for trigger button\n    if (this.sortDropdownTrigger) {\n      const sortByLabel = this.sortDropdownTrigger.getAttribute(ATTR.ARIA_LABEL).split(',')[0];\n      if (hasSelection) {\n        this.sortDropdownTrigger.setAttribute(ATTR.ARIA_LABEL, `${sortByLabel}, ${text}`);\n      } else {\n        this.sortDropdownTrigger.setAttribute(ATTR.ARIA_LABEL, sortByLabel);\n      }\n    }\n\n    // Update active state and ARIA attributes\n    this.sortOptions.forEach((option) =>
1 {\n      option.classList.remove(CLASSES.ACTIVE);\n      option.setAttribute(ATTR.ARIA_SELECTED, 'false');\n      option.setAttribute(ATTR.TABINDEX, DEFAULTS.TABINDEX_INACTIVE);\n    });\n\n    const activeOption = this.querySelector(`[${ATTR.SORT_OPTION}=\"${value}\"]`);\n    if (activeOption) {\n      activeOption.classList.add(CLASSES.ACTIVE);\n      activeOption.setAttribute(ATTR.ARIA_SELECTED, 'true');\n      activeOption.setAttribute(ATTR.TABINDEX, DEFAULTS.TABINDEX_ACTIVE);\n    }\n  }\n\n  /**\n   * Apply sort selection to URL and trigger sort change event\n   * @private\n   * @param {string} sortValue - Selected sort value\n   */\n  applySortToURL(sortValue) {\n    try {\n      const url = new URL(window.location.href);\n\n      // Always set the sort parameter explicitly, even for default sort\n      url.searchParams.set(URL_PARAMS.SORT_BY, sortValue);\n\n      // Reset to first page when sort changes\n      url.searchParams.delete(URL_PARAMS.PAGE);\n\n      // Update browser URL first\n      window.history.pushState({}, '', url.toString());\n\n      // Small delay to ensure URL is updated, then trigger event\n      setTimeout(() => {\n        document.dispatchEvent(\n          new CustomEvent('sort-changed', {\n            detail: { sortValue, url: url.toString() },\n            bubbles: true,\n          }),\n        );\n      }, TIMING.URL_UPDATE_DELAY);\n    } catch (error) {\n      console.error('Error applying sort to URL:', error);\n      this.isProcessing = false;\n    }\n  }\n\n  /**\n   * Load saved sort preference from URL parameters\n   * @private\n   */\n  loadSavedSort() {\n    const urlParams = new URLSearchParams(window.location.search);\n    const urlSort = urlParams.get(URL_PARAMS.SORT_BY);\n\n    // Determine if sort was explicitly set\n    let sortValue = DEFAULTS.SORT_VALUE; // Default fallback\n    let showText = false;\n\n    if (urlSort) {\n      // URL parameter exists, show the selected option\n      sortValue = urlSort;\n      showText = true;\n    } else {\n      // No URL parameter, use default but don't show text initially\n      showText = false;\n    }\n\n    // Set current value\n    this.currentSortValue = sortValue;\n\n    // Find the option and set it as active\n    const option = this.querySelector(`[${ATTR.SORT_OPTION}=\"${sortValue}\"]`);\n    if (option) {\n      const sortText = option.textContent.trim();\n      this.setSortOption(sortValue, sortText, showText);\n    }\n  }\n\n  /**\n   * Reset the sort dropdown to default state (no selection shown)\n   * @public\n   */\n  reset() {\n    const defaultOption = this.querySelector(`[${ATTR.SORT_OPTION}=\"${DEFAULTS.SORT_VALUE}\"]`);\n    if (defaultOption) {\n      const sortText = defaultOption.textContent.trim();\n      this.setSortOption(DEFAULTS.SORT_VALUE, sortText, false);\n    }\n  }\n\n  /**\n   * Handle keyboard navigation on trigger button\n   * @private\n   * @param {KeyboardEvent} event - Keyboard event\n   */\n  handleTriggerKeydown(event) {\n    switch (event.key) {\n      case KEYS.ENTER:\n      case KEYS.SPACE:\n      case KEYS.ARROW_DOWN:\n      case KEYS.ARROW_UP:\n        event.preventDefault();\n        this.openSortDropdown();\n        this.focusFirstOption();\n        break;\n      case KEYS.ESCAPE:\n        this.closeSortDropdown();\n        break;\n    }\n  }\n\n  /**\n   * Handle keyboard navigation on dropdown options\n   * @private\n   * @param {KeyboardEvent} event - Keyboard event\n   */\n  handleOptionKeydown(event) {\n    switch (event.key) {\n      case KEYS.ENTER:\n      case KEYS.SPACE:\n        event.preventDefault();\n        this.handleSortChange(event);\n        this.sortDropdownTrigger.focus();\n        break;\n      case KEYS.ARROW_DOWN:\n        event.preventDefault();\n        this.focusNextOption(event.target);\n        break;\n      case KEYS.ARROW_UP:\n        event.preventDefault();\n        this.focusPreviousOption(event.target);\n        break;\n      case KEYS.ESCAPE:\n        event.preventDefault();\n        this.closeSortDropdown();\n        this.sortDropdownTrigger.focus();\n        break;\n      case KEYS.TAB:\n        this.closeSortDropdown();\n        break;\n    }\n  }\n\n  /**\n   * Handle document-level keyboard events (escape key)\n   * @private\n   * @param {KeyboardEvent} event - Keyboard event\n   */\n  handleDocumentKeydown(event) {\n    if (event.key === KEYS.ESCAPE && !this.sortDropdown.hasAttribute(ATTR.HIDDEN)) {\n      this.closeSortDropdown();\n      this.sortDropdownTrigger.focus();\n    }\n  }\n\n  /**\n   * Focus the first option in the dropdown\n   * @private\n   */\n  focusFirstOption() {\
1n    // Set all options to tabindex -1\n    this.sortOptions.forEach((option) =>\n      option.setAttribute(ATTR.TABINDEX, DEFAULTS.TABINDEX_INACTIVE),\n    );\n\n    const firstOption = this.sortOptions[0];\n    if (firstOption) {\n      firstOption.setAttribute(ATTR.TABINDEX, DEFAULTS.TABINDEX_ACTIVE);\n      firstOption.focus();\n    }\n  }\n\n  /**\n   * Focus the next option in the dropdown\n   * @private\n   * @param {HTMLElement} currentOption - Currently focused option\n   */\n  focusNextOption(currentOption) {\n    const currentIndex = Array.from(this.sortOptions).indexOf(currentOption);\n    const nextIndex = (currentIndex + 1) % this.sortOptions.length;\n\n    // Update tabindex\n    this.sortOptions.forEach((option) =>\n      option.setAttribute(ATTR.TABINDEX, DEFAULTS.TABINDEX_INACTIVE),\n    );\n    this.sortOptions[nextIndex].setAttribute(ATTR.TABINDEX, DEFAULTS.TABINDEX_ACTIVE);\n    this.sortOptions[nextIndex].focus();\n  }\n\n  /**\n   * Focus the previous option in the dropdown\n   * @private\n   * @param {HTMLElement} currentOption - Currently focused option\n   */\n  focusPreviousOption(currentOption) {\n    const currentIndex = Array.from(this.sortOptions).indexOf(currentOption);\n    const previousIndex = currentIndex === 0 ? this.sortOptions.length - 1 : currentIndex - 1;\n\n    // Update tabindex\n    this.sortOptions.forEach((option) =>\n      option.setAttribute(ATTR.TABINDEX, DEFAULTS.TABINDEX_INACTIVE),\n    );\n    this.sortOptions[previousIndex].setAttribute(ATTR.TABINDEX, DEFAULTS.TABINDEX_ACTIVE);\n    this.sortOptions[previousIndex].focus();\n  }\n}\n\nif (!customElements.get('sortby-dropdown-component')) {\n  customElements.define('sortby-dropdown-component', SortbyDropdownComponent);\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/snippet.sort-by-dropdown.js?\n}")},
1"./src/snippets/sort-by-dropdown.css":function(__unused_webpack_module,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/sort-by-dropdown.css?\n}")}
1},__webpack_module_cache__={};function __webpack_require__(moduleId){var cachedModule=__webpack_module_cache__[moduleId];if(cachedModule!==void 0)return cachedModule.exports;var module=__webpack_module_cache__[moduleId]={exports:{}};return __webpack_modules__[moduleId](module,module.exports,__webpack_require__),module.exports}(function(){__webpack_require__.r=function(exports){typeof Symbol!="undefined"&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(exports,"__esModule",{value:!0})}})();var __webpack_exports__=__webpack_require__("./src/snippets/snippet.sort-by-dropdown.js")})();
2//# sourceMappingURL=/cdn/shop/t/239/assets/snippet.sort-by-dropdown.js.map?v=35690426143889497741790728944

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.