@designbasekorea/ui-wc 0.5.3 → 0.5.6

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.
Files changed (156) hide show
  1. package/dist/chunks/lottie-e273b936.js +17032 -0
  2. package/dist/esm/components/animation-background/canvas-controller.js +121 -1
  3. package/dist/esm/components/animation-background/controls.js +180 -1
  4. package/dist/esm/components/animation-background/graphite-controller.js +196 -1
  5. package/dist/esm/components/animation-background/graphite-presets.js +42 -1
  6. package/dist/esm/components/animation-background/graphite-shader.js +111 -1
  7. package/dist/esm/components/animation-background/layers.js +369 -1
  8. package/dist/esm/components/animation-background/lumina-presets.js +53 -1
  9. package/dist/esm/components/animation-background/lumina-shaders.js +181 -1
  10. package/dist/esm/components/animation-background/lumina-utils.js +60 -1
  11. package/dist/esm/components/animation-background/particle-defaults.js +36 -1
  12. package/dist/esm/components/animation-background/renderers.js +167 -1
  13. package/dist/esm/components/animation-background/shader-controller.js +154 -1
  14. package/dist/esm/components/color-picker-utils.js +134 -1
  15. package/dist/esm/components/date-picker-utils.js +140 -1
  16. package/dist/esm/components/db-accordion.js +304 -1
  17. package/dist/esm/components/db-ad-banner.js +455 -1
  18. package/dist/esm/components/db-alert.js +149 -1
  19. package/dist/esm/components/db-animation-background.js +486 -1
  20. package/dist/esm/components/db-animation-text.js +601 -1
  21. package/dist/esm/components/db-app-badge.js +88 -1
  22. package/dist/esm/components/db-audio-player.js +752 -1
  23. package/dist/esm/components/db-avatar.js +188 -1
  24. package/dist/esm/components/db-backdrop.js +139 -1
  25. package/dist/esm/components/db-badge.js +91 -1
  26. package/dist/esm/components/db-banner.js +188 -1
  27. package/dist/esm/components/db-bottom-navigation.js +146 -1
  28. package/dist/esm/components/db-bottom-sheet.js +481 -1
  29. package/dist/esm/components/db-breadcrumbs.js +329 -1
  30. package/dist/esm/components/db-button.js +316 -1
  31. package/dist/esm/components/db-calendar.js +675 -1
  32. package/dist/esm/components/db-card.js +265 -1
  33. package/dist/esm/components/db-carousel.js +264 -1
  34. package/dist/esm/components/db-checkbox.js +202 -1
  35. package/dist/esm/components/db-chip.js +104 -1
  36. package/dist/esm/components/db-code-block.js +304 -1
  37. package/dist/esm/components/db-color-picker.js +746 -1
  38. package/dist/esm/components/db-confirm.js +191 -1
  39. package/dist/esm/components/db-container.js +39 -1
  40. package/dist/esm/components/db-context-menu.js +265 -1
  41. package/dist/esm/components/db-countdown.js +146 -1
  42. package/dist/esm/components/db-cursor-follower.js +173 -1
  43. package/dist/esm/components/db-date-picker.js +837 -1
  44. package/dist/esm/components/db-divider.js +66 -1
  45. package/dist/esm/components/db-drawer.js +245 -1
  46. package/dist/esm/components/db-dropdown.js +493 -1
  47. package/dist/esm/components/db-dropzone.js +266 -1
  48. package/dist/esm/components/db-empty-state.js +97 -1
  49. package/dist/esm/components/db-file-uploader.js +256 -1
  50. package/dist/esm/components/db-floating-action-button.js +139 -1
  51. package/dist/esm/components/db-footer.js +385 -1
  52. package/dist/esm/components/db-form.js +566 -1
  53. package/dist/esm/components/db-gantt.js +882 -1
  54. package/dist/esm/components/db-gradient.js +133 -1
  55. package/dist/esm/components/db-grid-background.js +909 -1
  56. package/dist/esm/components/db-grid.js +73 -1
  57. package/dist/esm/components/db-icons.js +82 -1
  58. package/dist/esm/components/db-image-list.js +214 -1
  59. package/dist/esm/components/db-image-placeholder.js +96 -1
  60. package/dist/esm/components/db-image.js +289 -1
  61. package/dist/esm/components/db-indicator.js +517 -1
  62. package/dist/esm/components/db-input.js +420 -1
  63. package/dist/esm/components/db-kanban.js +538 -1
  64. package/dist/esm/components/db-label.js +66 -1
  65. package/dist/esm/components/db-lightbox.js +372 -1
  66. package/dist/esm/components/db-list.js +340 -1
  67. package/dist/esm/components/db-logo-svg.js +72 -1
  68. package/dist/esm/components/db-logo.js +106 -1
  69. package/dist/esm/components/db-lottie.js +109 -1
  70. package/dist/esm/components/db-markdown-editor.js +458 -1
  71. package/dist/esm/components/db-marquee.js +402 -1
  72. package/dist/esm/components/db-masonry.js +94 -1
  73. package/dist/esm/components/db-menu-item.js +211 -1
  74. package/dist/esm/components/db-modal.js +522 -1
  75. package/dist/esm/components/db-navbar.js +680 -1
  76. package/dist/esm/components/db-onboarding-modal.js +322 -1
  77. package/dist/esm/components/db-page-header.js +312 -1
  78. package/dist/esm/components/db-pagination.js +271 -1
  79. package/dist/esm/components/db-popover.js +580 -1
  80. package/dist/esm/components/db-progress-step.js +166 -1
  81. package/dist/esm/components/db-progress.js +171 -1
  82. package/dist/esm/components/db-progressbar.js +72 -1
  83. package/dist/esm/components/db-radio.js +172 -1
  84. package/dist/esm/components/db-random-gradient.js +121 -1
  85. package/dist/esm/components/db-range-slider.js +811 -1
  86. package/dist/esm/components/db-rating.js +325 -1
  87. package/dist/esm/components/db-reorder.js +286 -1
  88. package/dist/esm/components/db-resizable-panels.js +282 -1
  89. package/dist/esm/components/db-scroll-area.js +126 -1
  90. package/dist/esm/components/db-search-bar.js +668 -1
  91. package/dist/esm/components/db-section-app-download.js +101 -1
  92. package/dist/esm/components/db-section-contact.js +102 -1
  93. package/dist/esm/components/db-section-cta.js +150 -1
  94. package/dist/esm/components/db-section-faq.js +120 -1
  95. package/dist/esm/components/db-section-feature-grid.js +137 -1
  96. package/dist/esm/components/db-section-feature.js +179 -1
  97. package/dist/esm/components/db-section-hero.js +243 -1
  98. package/dist/esm/components/db-section-logo-cloud.js +138 -1
  99. package/dist/esm/components/db-section-pricing.js +166 -1
  100. package/dist/esm/components/db-section-stats.js +129 -1
  101. package/dist/esm/components/db-section-testimonials.js +252 -1
  102. package/dist/esm/components/db-section.js +385 -1
  103. package/dist/esm/components/db-segment-control.js +231 -1
  104. package/dist/esm/components/db-select.js +571 -1
  105. package/dist/esm/components/db-share-icons.js +40 -1
  106. package/dist/esm/components/db-share.js +489 -1
  107. package/dist/esm/components/db-sidebar.js +1068 -1
  108. package/dist/esm/components/db-skeleton.js +94 -1
  109. package/dist/esm/components/db-spinner.js +102 -1
  110. package/dist/esm/components/db-split-view.js +297 -1
  111. package/dist/esm/components/db-stack.js +46 -1
  112. package/dist/esm/components/db-stat.js +218 -1
  113. package/dist/esm/components/db-stepper.js +398 -1
  114. package/dist/esm/components/db-table.js +412 -1
  115. package/dist/esm/components/db-tabs.js +335 -1
  116. package/dist/esm/components/db-testimonial.js +162 -1
  117. package/dist/esm/components/db-textarea.js +233 -1
  118. package/dist/esm/components/db-time-picker.js +523 -1
  119. package/dist/esm/components/db-timeline.js +174 -1
  120. package/dist/esm/components/db-toast.js +158 -1
  121. package/dist/esm/components/db-toggle.js +162 -1
  122. package/dist/esm/components/db-toolbar.js +94 -1
  123. package/dist/esm/components/db-tooltip.js +440 -1
  124. package/dist/esm/components/db-top-banner.js +121 -1
  125. package/dist/esm/components/db-tutorial.js +324 -1
  126. package/dist/esm/components/db-video-player.js +503 -1
  127. package/dist/esm/components/db-youtube-player.js +194 -1
  128. package/dist/esm/components/highlight-code.js +65 -1
  129. package/dist/esm/components/input-validation.js +50 -1
  130. package/dist/esm/components/marketing-block-host-utils.js +9 -1
  131. package/dist/esm/components/number-scrub.js +117 -1
  132. package/dist/esm/components/random-gradient-palettes.js +67 -1
  133. package/dist/esm/components/section-block-header-utils.js +48 -1
  134. package/dist/esm/helpers/confirm.js +52 -1
  135. package/dist/esm/helpers/toast.js +73 -1
  136. package/dist/esm/index.js +111 -1
  137. package/dist/esm/node_modules/prismjs/components/prism-bash.js +235 -1
  138. package/dist/esm/node_modules/prismjs/components/prism-clike.js +31 -1
  139. package/dist/esm/node_modules/prismjs/components/prism-css.js +64 -1
  140. package/dist/esm/node_modules/prismjs/components/prism-javascript.js +172 -1
  141. package/dist/esm/node_modules/prismjs/components/prism-json.js +27 -1
  142. package/dist/esm/node_modules/prismjs/components/prism-jsx.js +143 -1
  143. package/dist/esm/node_modules/prismjs/components/prism-markup.js +186 -1
  144. package/dist/esm/node_modules/prismjs/components/prism-scss.js +81 -1
  145. package/dist/esm/node_modules/prismjs/components/prism-tsx.js +15 -1
  146. package/dist/esm/node_modules/prismjs/components/prism-typescript.js +60 -1
  147. package/dist/index.cjs +55414 -1
  148. package/dist/index.css +1 -1
  149. package/dist/index.esm.js +38284 -1
  150. package/dist/react/chunks/lottie-72a4c6d4.js +17032 -0
  151. package/dist/react/index.cjs +56503 -1
  152. package/dist/react/index.d.ts +1 -1
  153. package/dist/react/index.esm.js +39373 -1
  154. package/package.json +1 -1
  155. package/dist/chunks/lottie-2d1f6278.js +0 -1
  156. package/dist/react/chunks/lottie-14e7addb.js +0 -1
@@ -1 +1,752 @@
1
- import{createDbIcon as t}from"./db-icons.js";const e="designbase-wc-audio-player";class DbAudioPlayer extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.isPlaying=!1,this.isMuted=!1,this.volume=1,this.currentTime=0,this.duration=0,this.showSettingsMenu=!1,this.playbackRate=1,this.currentRepeatMode="none",this.isShuffle=!1,this.isLoading=!1,this.error=null,this.playlistIndex=0,this.shuffledPlaylist=[],this.boundKeyDown=t=>this.handleKeyDown(t)}static get observedAttributes(){return["src","title","artist","album","album-art","size","variant","theme","auto-play","loop","muted","show-controls","enable-keyboard","show-progress","show-time","show-volume","show-settings","playlist","current-index","playback-rates","default-playback-rate","repeat-mode","shuffle"]}connectedCallback(){this.style.display="block",this.built||(this.built=!0,this.build(),this.bindAudioEvents()),this.syncFromAttributes(),this.updateUI(),this.getBool("enable-keyboard",!0)&&document.addEventListener("keydown",this.boundKeyDown)}disconnectedCallback(){document.removeEventListener("keydown",this.boundKeyDown)}attributeChangedCallback(t){if(this.built){if("current-index"===t)return this.playlistIndex=Number(this.getAttribute("current-index")||0),void this.loadCurrentTrack();"repeat-mode"===t&&(this.currentRepeatMode=this.getAttribute("repeat-mode")||"none"),"shuffle"===t&&(this.isShuffle=this.getBool("shuffle"),this.refreshShuffledPlaylist()),"default-playback-rate"===t&&(this.playbackRate=Number(this.getAttribute("default-playback-rate")||1),this.audioEl&&(this.audioEl.playbackRate=this.playbackRate)),this.updateUI()}}getBool(t,e=!1){if(!this.hasAttribute(t))return e;const s=this.getAttribute(t);return""===s||"true"===s}getPlaylist(){try{const t=this.getAttribute("playlist"),e=t?JSON.parse(t):[];return Array.isArray(e)?e:[]}catch{return[]}}getPlaybackRates(){try{const t=this.getAttribute("playback-rates"),e=t?JSON.parse(t):[.5,.75,1,1.25,1.5,2];return Array.isArray(e)&&e.length>0?e:[.5,.75,1,1.25,1.5,2]}catch{return[.5,.75,1,1.25,1.5,2]}}getIconSize(){const t=this.getAttribute("size")||"m";return"s"===t?16:"l"===t?24:"xl"===t?32:20}getCurrentTrack(){const t=this.getPlaylist();if(t.length>0){return t[this.isShuffle&&this.shuffledPlaylist.length>0?this.shuffledPlaylist[this.playlistIndex]??this.playlistIndex:this.playlistIndex]||t[0]}return{src:this.getAttribute("src")||"",title:this.getAttribute("title")||void 0,artist:this.getAttribute("artist")||void 0,album:this.getAttribute("album")||void 0,albumArt:this.getAttribute("album-art")||void 0}}refreshShuffledPlaylist(){const t=this.getPlaylist();if(t.length>0&&this.isShuffle){const e=Array.from({length:t.length},(t,e)=>e);this.shuffledPlaylist=[...e].sort(()=>Math.random()-.5)}else this.shuffledPlaylist=[]}formatTime(t){return`${Math.floor(t/60)}:${Math.floor(t%60).toString().padStart(2,"0")}`}syncFromAttributes(){this.playlistIndex=Number(this.getAttribute("current-index")||0),this.currentRepeatMode=this.getAttribute("repeat-mode")||"none",this.isShuffle=this.getBool("shuffle"),this.playbackRate=Number(this.getAttribute("default-playback-rate")||1),this.isMuted=this.getBool("muted"),this.volume=this.isMuted?0:1,this.refreshShuffledPlaylist(),this.loadCurrentTrack()}loadCurrentTrack(){if(!this.audioEl)return;const t=this.getCurrentTrack(),e=t?.src||this.getAttribute("src")||"";this.audioEl.src!==e&&e&&(this.audioEl.src=e,this.audioEl.load()),this.updateTrackInfo(),this.updatePlaylistActive()}bindAudioEvents(){const t=this.audioEl;t&&(t.addEventListener("play",()=>{this.isPlaying=!0,this.dispatchEvent(new CustomEvent("db-play",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("pause",()=>{this.isPlaying=!1,this.dispatchEvent(new CustomEvent("db-pause",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("timeupdate",()=>{this.currentTime=t.currentTime,this.duration=t.duration||0,this.dispatchEvent(new CustomEvent("db-time-update",{detail:{currentTime:this.currentTime,duration:this.duration},bubbles:!0,composed:!0})),this.updateProgress(),this.updateTime()}),t.addEventListener("volumechange",()=>{this.volume=t.volume,this.isMuted=t.muted,this.dispatchEvent(new CustomEvent("db-volume-change",{detail:{volume:this.volume},bubbles:!0,composed:!0})),this.updateVolumeUI()}),t.addEventListener("ended",()=>this.handleEnded()),t.addEventListener("error",()=>{this.error="오디오를 재생할 수 없습니다.",this.dispatchEvent(new CustomEvent("db-error",{detail:{error:this.error},bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("loadstart",()=>{this.isLoading=!0,this.error=null,this.updateUI()}),t.addEventListener("canplay",()=>{this.isLoading=!1,this.updateUI()}))}handleEnded(){this.isPlaying=!1,this.dispatchEvent(new CustomEvent("db-ended",{bubbles:!0,composed:!0}));const t=this.getPlaylist();if(t.length>0){if("one"===this.currentRepeatMode)this.audioEl&&(this.audioEl.currentTime=0,this.audioEl.play());else if("all"===this.currentRepeatMode||this.playlistIndex<t.length-1){const e=this.playlistIndex+1;e<t.length?this.setPlaylistIndex(e):"all"===this.currentRepeatMode&&this.setPlaylistIndex(0)}}else this.getBool("loop")&&this.audioEl&&(this.audioEl.currentTime=0,this.audioEl.play());this.updateUI()}setPlaylistIndex(t){this.playlistIndex=t,this.dispatchEvent(new CustomEvent("db-playlist-change",{detail:{index:t},bubbles:!0,composed:!0})),this.loadCurrentTrack(),this.audioEl&&this.audioEl.play()}togglePlay(){this.audioEl&&(this.isPlaying?this.audioEl.pause():this.audioEl.play())}toggleMute(){this.audioEl&&(this.audioEl.muted=!this.isMuted)}toggleRepeatMode(){const t=["none","one","all"],e=t[(t.indexOf(this.currentRepeatMode)+1)%t.length];this.currentRepeatMode=e,this.dispatchEvent(new CustomEvent("db-repeat-mode-change",{detail:{mode:e},bubbles:!0,composed:!0})),this.updateUI()}toggleShuffle(){this.isShuffle=!this.isShuffle,this.refreshShuffledPlaylist(),this.dispatchEvent(new CustomEvent("db-shuffle-change",{detail:{shuffle:this.isShuffle},bubbles:!0,composed:!0})),this.updateUI()}handlePlaybackRateChange(t){this.audioEl&&(this.audioEl.playbackRate=t,this.playbackRate=t,this.dispatchEvent(new CustomEvent("db-playback-rate-change",{detail:{rate:t},bubbles:!0,composed:!0})))}handleKeyDown(t){if(this.audioEl&&this.getBool("enable-keyboard",!0))switch(t.code){case"Space":t.preventDefault(),this.togglePlay();break;case"ArrowLeft":t.preventDefault(),this.audioEl.currentTime=Math.max(0,this.currentTime-10);break;case"ArrowRight":t.preventDefault(),this.audioEl.currentTime=Math.min(this.duration,this.currentTime+10);break;case"ArrowUp":t.preventDefault(),this.audioEl.volume=Math.min(1,this.volume+.1),this.audioEl.muted=!1;break;case"ArrowDown":t.preventDefault(),this.audioEl.volume=Math.max(0,this.volume-.1),this.audioEl.muted=0===this.audioEl.volume;break;case"KeyM":t.preventDefault(),this.toggleMute();break;case"KeyR":t.preventDefault(),this.toggleRepeatMode();break;case"KeyS":t.preventDefault(),this.toggleShuffle()}}iconButton(s,i,l){const a=document.createElement("button");return a.type="button",a.className=`${e}__control-button`,a.append(t(s,this.getIconSize())),l?.disabled&&(a.disabled=!0),l?.data&&Object.entries(l.data).forEach(([t,e])=>a.setAttribute(`data-${t}`,e)),a.addEventListener("click",i),a}build(){const t=this.getAttribute("size")||"m",s=this.getAttribute("variant")||"default",i=this.getAttribute("theme")||"auto";this.className=[e,`${e}--size-${t}`,`${e}--variant-${s}`,`${e}--theme-${i}`].join(" "),this.audioEl=document.createElement("audio"),this.audioEl.preload="metadata",this.audioEl.hidden=!0;const l=document.createElement("div");l.className=`${e}__loading`;const a=document.createElement("div");a.className=`${e}__spinner`,l.append(a);const n=document.createElement("div");n.className=`${e}__error`;const o=document.createElement("p");o.textContent="오디오를 재생할 수 없습니다.";const r=document.createElement("button");r.type="button",r.textContent="다시 시도",r.addEventListener("click",()=>window.location.reload()),n.append(o,r);const h=document.createElement("div");h.className=`${e}__album-art`,this.albumImgEl=document.createElement("img"),this.albumImgEl.addEventListener("error",()=>{this.albumImgEl&&(this.albumImgEl.style.display="none"),this.albumPlaceholderEl?.classList.remove("album-placeholder")}),this.albumPlaceholderEl=document.createElement("div"),this.albumPlaceholderEl.className="album-placeholder",this.albumPlaceholderEl.textContent="🎵",h.append(this.albumImgEl,this.albumPlaceholderEl);const u=document.createElement("div");u.className=`${e}__main`;const d=document.createElement("div");d.className=`${e}__info`,this.titleEl=document.createElement("div"),this.titleEl.className=`${e}__title`,this.artistEl=document.createElement("div"),this.artistEl.className=`${e}__artist`,this.albumEl=document.createElement("div"),this.albumEl.className=`${e}__album`,d.append(this.titleEl,this.artistEl,this.albumEl),this.progressEl=document.createElement("div"),this.progressEl.className=`${e}__progress`,this.progressBarEl=document.createElement("div"),this.progressBarEl.className=`${e}__progress-bar`,this.progressHandleEl=document.createElement("div"),this.progressHandleEl.className=`${e}__progress-handle`,this.progressEl.append(this.progressBarEl,this.progressHandleEl),this.progressEl.addEventListener("click",t=>{if(!this.audioEl||!this.progressEl)return;const e=this.progressEl.getBoundingClientRect(),s=(t.clientX-e.left)/e.width;this.audioEl.currentTime=s*(this.duration||0)});const c=document.createElement("div");c.className=`${e}__controls`;const m=document.createElement("div");m.className=`${e}__left-controls`;const p=document.createElement("div");p.className=`${e}__center-controls`;const E=document.createElement("div");E.className=`${e}__right-controls`,this.playButtonEl=document.createElement("button"),this.playButtonEl.type="button",this.playButtonEl.className=`${e}__play-button`,this.playButtonEl.addEventListener("click",()=>this.togglePlay()),p.append(this.playButtonEl),c.append(m,p,E);const g=document.createElement("div");g.className=`${e}__bottom-controls`;const b=document.createElement("div");b.className=`${e}__left-bottom`;const y=document.createElement("div");y.className=`${e}__right-bottom`;const v=document.createElement("div");v.className=`${e}__time`,this.timeCurrentEl=document.createElement("span");const f=document.createElement("span");f.textContent="/",this.timeDurationEl=document.createElement("span"),v.append(this.timeCurrentEl,f,this.timeDurationEl),b.append(v);const _=document.createElement("div");_.className=`${e}__volume`;const k=this.iconButton(this.isMuted||0===this.volume?"mute-filled":"volume-up",()=>this.toggleMute());k.title=this.isMuted||0===this.volume?"음소거 해제":"음소거";const C=document.createElement("div");C.className=`${e}__volume-slider`;const I=document.createElement("div");I.className="volume-label";const $=document.createElement("span");$.textContent="🔊";const P=document.createElement("span");I.append($,P),this.volumeInputEl=document.createElement("input"),this.volumeInputEl.type="range",this.volumeInputEl.min="0",this.volumeInputEl.max="1",this.volumeInputEl.step="0.1",this.volumeInputEl.addEventListener("input",()=>{if(!this.audioEl||!this.volumeInputEl)return;const t=parseFloat(this.volumeInputEl.value);this.audioEl.volume=t,this.audioEl.muted=0===t}),C.append(I,this.volumeInputEl),_.append(k,C);const A=document.createElement("div");A.className=`${e}__settings`;const x=this.iconButton("settings",()=>{this.showSettingsMenu=!this.showSettingsMenu,this.updateSettingsMenu()});this.settingsMenuEl=document.createElement("div"),this.settingsMenuEl.className=`${e}__settings-menu`,this.settingsMenuEl.hidden=!0;const M=document.createElement("div");M.className=`${e}__setting-group`;const w=document.createElement("label");w.textContent="재생 속도";const B=document.createElement("select");B.addEventListener("change",()=>this.handlePlaybackRateChange(parseFloat(B.value))),M.append(w,B),this.settingsMenuEl.append(M),A.append(x,this.settingsMenuEl),y.append(_,A),g.append(b,y),u.append(d,this.progressEl,c,g);const N=document.createElement("div");N.className=`${e}__playlist`,N.hidden=!0;const T=document.createElement("h4");T.textContent="플레이리스트",this.playlistTracksEl=document.createElement("div"),this.playlistTracksEl.className=`${e}__playlist-tracks`,N.append(T,this.playlistTracksEl),this.append(this.audioEl,l,n,h,u,N),this._leftControls=m,this._rightControls=E,this._controls=c,this._time=v,this._volumeWrap=_,this._settings=A,this._playlist=N,this._rateSelect=B,this._volumePercent=P,this._muteButton=k}get leftControls(){return this._leftControls}get rightControls(){return this._rightControls}get controlsEl(){return this._controls}get timeEl(){return this._time}get volumeWrap(){return this._volumeWrap}get settingsEl(){return this._settings}get playlistEl(){return this._playlist}get rateSelect(){return this._rateSelect}get volumePercent(){return this._volumePercent}get muteButton(){return this._muteButton}updateTrackInfo(){const t=this.getCurrentTrack();this.titleEl&&(this.titleEl.textContent=t?.title||"Unknown Track"),this.artistEl&&(this.artistEl.textContent=t?.artist||"Unknown Artist"),this.albumEl&&(t?.album?(this.albumEl.textContent=t.album,this.albumEl.hidden=!1):this.albumEl.hidden=!0),this.albumImgEl&&this.albumPlaceholderEl&&(t?.albumArt?(this.albumImgEl.src=t.albumArt,this.albumImgEl.alt=t.title||"Album Art",this.albumImgEl.style.display=""):(this.albumImgEl.removeAttribute("src"),this.albumImgEl.style.display="none"),this.albumPlaceholderEl.className="album-placeholder")}updateProgress(){const t=this.duration>0?this.currentTime/this.duration*100:0;this.progressBarEl&&(this.progressBarEl.style.width=`${t}%`),this.progressHandleEl&&(this.progressHandleEl.style.left=`${t}%`,this.progressHandleEl.style.transform=t<=0?"translate(0, -50%)":t>=100?"translate(-100%, -50%)":"translate(-50%, -50%)")}updateTime(){this.timeCurrentEl&&(this.timeCurrentEl.textContent=this.formatTime(this.currentTime)),this.timeDurationEl&&(this.timeDurationEl.textContent=this.formatTime(this.duration))}updateVolumeUI(){this.volumeInputEl&&(this.volumeInputEl.value=String(this.volume)),this.volumePercent&&(this.volumePercent.textContent=`${Math.round(100*this.volume)}%`),this.muteButton&&(this.muteButton.replaceChildren(t(this.isMuted||0===this.volume?"mute-filled":"volume-up",this.getIconSize())),this.muteButton.title=this.isMuted||0===this.volume?"음소거 해제":"음소거")}updateSettingsMenu(){this.settingsMenuEl&&(this.settingsMenuEl.hidden=!this.showSettingsMenu)}rebuildControlButtons(){const e=this.getPlaylist(),s=1.2*this.getIconSize();this.leftControls.replaceChildren(),this.rightControls.replaceChildren(),e.length>0&&this.leftControls.append(this.iconButton("refresh",()=>this.toggleShuffle(),{data:{shuffle:"true"}})),this.leftControls.append(this.iconButton("chevron-left",()=>{if(e.length>0){const t=this.playlistIndex-1;t>=0&&this.setPlaylistIndex(t)}else this.audioEl&&(this.audioEl.currentTime=0)},{disabled:e.length>0&&0===this.playlistIndex})),this.playButtonEl&&this.playButtonEl.replaceChildren(t(this.isPlaying?"pause":"play",s)),this.rightControls.append(this.iconButton("chevron-right",()=>{if(e.length>0){const t=this.playlistIndex+1;t<e.length&&this.setPlaylistIndex(t)}},{disabled:e.length>0&&this.playlistIndex===e.length-1})),e.length>0&&this.rightControls.append(this.iconButton("repeat",()=>this.toggleRepeatMode(),{data:{repeat:"true"}}))}rebuildPlaylist(){if(!this.playlistTracksEl)return;const t=this.getPlaylist(),s=this.getAttribute("variant")||"default";this.playlistEl.hidden=!("full"===s&&t.length>0),this.playlistTracksEl.replaceChildren(),t.forEach((t,s)=>{const i=document.createElement("div");if(i.className=`${e}__playlist-track${s===this.playlistIndex?` ${e}__playlist-track--active`:""}`,i.addEventListener("click",()=>this.setPlaylistIndex(s)),t.albumArt){const e=document.createElement("img");e.src=t.albumArt,e.alt=t.title||"Album Art",i.append(e)}const l=document.createElement("div");l.className=`${e}__playlist-track-info`;const a=document.createElement("div");a.className=`${e}__playlist-track-title`,a.textContent=t.title||"Unknown Track";const n=document.createElement("div");n.className=`${e}__playlist-track-artist`,n.textContent=t.artist||"Unknown Artist",l.append(a,n),i.append(l),this.playlistTracksEl?.append(i)})}updatePlaylistActive(){this.playlistTracksEl&&Array.from(this.playlistTracksEl.children).forEach((t,s)=>{t.classList.toggle(`${e}__playlist-track--active`,s===this.playlistIndex)})}updateUI(){const t=this.getAttribute("size")||"m",s=this.getAttribute("variant")||"default",i=this.getAttribute("theme")||"auto",l=this.getBool("show-controls",!0),a=this.getBool("show-progress",!0),n=this.getBool("show-time",!0),o=this.getBool("show-volume",!0),r=this.getBool("show-settings",!1);this.className=[e,`${e}--size-${t}`,`${e}--variant-${s}`,`${e}--theme-${i}`,this.isPlaying&&`${e}--playing`,!this.isPlaying&&`${e}--paused`,this.isMuted&&`${e}--muted`,this.isLoading&&`${e}--loading`,this.error&&`${e}--error`,this.isShuffle&&`${e}--shuffle`,`${e}--repeat-${this.currentRepeatMode}`].filter(Boolean).join(" "),this.audioEl&&(this.audioEl.autoplay=this.getBool("auto-play"),this.audioEl.loop=this.getBool("loop"),this.audioEl.muted=this.getBool("muted"),this.audioEl.playbackRate=this.playbackRate),this.progressEl&&(this.progressEl.hidden=!a),this.controlsEl&&(this.controlsEl.hidden=!l),this.timeEl&&(this.timeEl.hidden=!n),this.volumeWrap&&(this.volumeWrap.hidden=!o),this.settingsEl&&(this.settingsEl.hidden=!r),this.rateSelect.replaceChildren(...this.getPlaybackRates().map(t=>{const e=document.createElement("option");return e.value=String(t),e.textContent=`${t}x`,t===this.playbackRate&&(e.selected=!0),e})),this.rebuildControlButtons(),this.updateTrackInfo(),this.updateProgress(),this.updateTime(),this.updateVolumeUI(),this.updateSettingsMenu(),this.rebuildPlaylist()}}customElements.get("db-audio-player")||customElements.define("db-audio-player",DbAudioPlayer);export{DbAudioPlayer};
1
+ import { createDbIcon } from './db-icons.js';
2
+
3
+ const CLASS = 'designbase-wc-audio-player';
4
+ class DbAudioPlayer extends HTMLElement {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.built = false;
8
+ this.isPlaying = false;
9
+ this.isMuted = false;
10
+ this.volume = 1;
11
+ this.currentTime = 0;
12
+ this.duration = 0;
13
+ this.showSettingsMenu = false;
14
+ this.playbackRate = 1;
15
+ this.currentRepeatMode = 'none';
16
+ this.isShuffle = false;
17
+ this.isLoading = false;
18
+ this.error = null;
19
+ this.playlistIndex = 0;
20
+ this.shuffledPlaylist = [];
21
+ this.boundKeyDown = (event) => this.handleKeyDown(event);
22
+ }
23
+ static get observedAttributes() {
24
+ return [
25
+ 'src',
26
+ 'title',
27
+ 'artist',
28
+ 'album',
29
+ 'album-art',
30
+ 'size',
31
+ 'variant',
32
+ 'theme',
33
+ 'auto-play',
34
+ 'loop',
35
+ 'muted',
36
+ 'show-controls',
37
+ 'enable-keyboard',
38
+ 'show-progress',
39
+ 'show-time',
40
+ 'show-volume',
41
+ 'show-settings',
42
+ 'playlist',
43
+ 'current-index',
44
+ 'playback-rates',
45
+ 'default-playback-rate',
46
+ 'repeat-mode',
47
+ 'shuffle',
48
+ ];
49
+ }
50
+ connectedCallback() {
51
+ this.style.display = 'block';
52
+ if (!this.built) {
53
+ this.built = true;
54
+ this.build();
55
+ this.bindAudioEvents();
56
+ }
57
+ this.syncFromAttributes();
58
+ this.updateUI();
59
+ if (this.getBool('enable-keyboard', true)) {
60
+ document.addEventListener('keydown', this.boundKeyDown);
61
+ }
62
+ }
63
+ disconnectedCallback() {
64
+ document.removeEventListener('keydown', this.boundKeyDown);
65
+ }
66
+ attributeChangedCallback(name) {
67
+ if (!this.built)
68
+ return;
69
+ if (name === 'current-index') {
70
+ this.playlistIndex = Number(this.getAttribute('current-index') || 0);
71
+ this.loadCurrentTrack();
72
+ return;
73
+ }
74
+ if (name === 'repeat-mode') {
75
+ this.currentRepeatMode = this.getAttribute('repeat-mode') || 'none';
76
+ }
77
+ if (name === 'shuffle') {
78
+ this.isShuffle = this.getBool('shuffle');
79
+ this.refreshShuffledPlaylist();
80
+ }
81
+ if (name === 'default-playback-rate') {
82
+ this.playbackRate = Number(this.getAttribute('default-playback-rate') || 1);
83
+ if (this.audioEl)
84
+ this.audioEl.playbackRate = this.playbackRate;
85
+ }
86
+ this.updateUI();
87
+ }
88
+ getBool(name, fallback = false) {
89
+ if (!this.hasAttribute(name))
90
+ return fallback;
91
+ const value = this.getAttribute(name);
92
+ return value === '' || value === 'true';
93
+ }
94
+ getPlaylist() {
95
+ try {
96
+ const raw = this.getAttribute('playlist');
97
+ const parsed = raw ? JSON.parse(raw) : [];
98
+ return Array.isArray(parsed) ? parsed : [];
99
+ }
100
+ catch {
101
+ return [];
102
+ }
103
+ }
104
+ getPlaybackRates() {
105
+ try {
106
+ const raw = this.getAttribute('playback-rates');
107
+ const parsed = raw ? JSON.parse(raw) : [0.5, 0.75, 1, 1.25, 1.5, 2];
108
+ return Array.isArray(parsed) && parsed.length > 0 ? parsed : [0.5, 0.75, 1, 1.25, 1.5, 2];
109
+ }
110
+ catch {
111
+ return [0.5, 0.75, 1, 1.25, 1.5, 2];
112
+ }
113
+ }
114
+ getIconSize() {
115
+ const size = this.getAttribute('size') || 'm';
116
+ if (size === 's')
117
+ return 16;
118
+ if (size === 'l')
119
+ return 24;
120
+ if (size === 'xl')
121
+ return 32;
122
+ return 20;
123
+ }
124
+ getCurrentTrack() {
125
+ const playlist = this.getPlaylist();
126
+ if (playlist.length > 0) {
127
+ const index = this.isShuffle && this.shuffledPlaylist.length > 0
128
+ ? this.shuffledPlaylist[this.playlistIndex] ?? this.playlistIndex
129
+ : this.playlistIndex;
130
+ return playlist[index] || playlist[0];
131
+ }
132
+ return {
133
+ src: this.getAttribute('src') || '',
134
+ title: this.getAttribute('title') || undefined,
135
+ artist: this.getAttribute('artist') || undefined,
136
+ album: this.getAttribute('album') || undefined,
137
+ albumArt: this.getAttribute('album-art') || undefined,
138
+ };
139
+ }
140
+ refreshShuffledPlaylist() {
141
+ const playlist = this.getPlaylist();
142
+ if (playlist.length > 0 && this.isShuffle) {
143
+ const indices = Array.from({ length: playlist.length }, (_, i) => i);
144
+ this.shuffledPlaylist = [...indices].sort(() => Math.random() - 0.5);
145
+ }
146
+ else {
147
+ this.shuffledPlaylist = [];
148
+ }
149
+ }
150
+ formatTime(time) {
151
+ const minutes = Math.floor(time / 60);
152
+ const seconds = Math.floor(time % 60);
153
+ return `${minutes}:${seconds.toString().padStart(2, '0')}`;
154
+ }
155
+ syncFromAttributes() {
156
+ this.playlistIndex = Number(this.getAttribute('current-index') || 0);
157
+ this.currentRepeatMode = this.getAttribute('repeat-mode') || 'none';
158
+ this.isShuffle = this.getBool('shuffle');
159
+ this.playbackRate = Number(this.getAttribute('default-playback-rate') || 1);
160
+ this.isMuted = this.getBool('muted');
161
+ this.volume = this.isMuted ? 0 : 1;
162
+ this.refreshShuffledPlaylist();
163
+ this.loadCurrentTrack();
164
+ }
165
+ loadCurrentTrack() {
166
+ if (!this.audioEl)
167
+ return;
168
+ const track = this.getCurrentTrack();
169
+ const nextSrc = track?.src || this.getAttribute('src') || '';
170
+ if (this.audioEl.src !== nextSrc && nextSrc) {
171
+ this.audioEl.src = nextSrc;
172
+ this.audioEl.load();
173
+ }
174
+ this.updateTrackInfo();
175
+ this.updatePlaylistActive();
176
+ }
177
+ bindAudioEvents() {
178
+ const audio = this.audioEl;
179
+ if (!audio)
180
+ return;
181
+ audio.addEventListener('play', () => {
182
+ this.isPlaying = true;
183
+ this.dispatchEvent(new CustomEvent('db-play', { bubbles: true, composed: true }));
184
+ this.updateUI();
185
+ });
186
+ audio.addEventListener('pause', () => {
187
+ this.isPlaying = false;
188
+ this.dispatchEvent(new CustomEvent('db-pause', { bubbles: true, composed: true }));
189
+ this.updateUI();
190
+ });
191
+ audio.addEventListener('timeupdate', () => {
192
+ this.currentTime = audio.currentTime;
193
+ this.duration = audio.duration || 0;
194
+ this.dispatchEvent(new CustomEvent('db-time-update', {
195
+ detail: { currentTime: this.currentTime, duration: this.duration },
196
+ bubbles: true,
197
+ composed: true,
198
+ }));
199
+ this.updateProgress();
200
+ this.updateTime();
201
+ });
202
+ audio.addEventListener('volumechange', () => {
203
+ this.volume = audio.volume;
204
+ this.isMuted = audio.muted;
205
+ this.dispatchEvent(new CustomEvent('db-volume-change', {
206
+ detail: { volume: this.volume },
207
+ bubbles: true,
208
+ composed: true,
209
+ }));
210
+ this.updateVolumeUI();
211
+ });
212
+ audio.addEventListener('ended', () => this.handleEnded());
213
+ audio.addEventListener('error', () => {
214
+ this.error = '오디오를 재생할 수 없습니다.';
215
+ this.dispatchEvent(new CustomEvent('db-error', {
216
+ detail: { error: this.error },
217
+ bubbles: true,
218
+ composed: true,
219
+ }));
220
+ this.updateUI();
221
+ });
222
+ audio.addEventListener('loadstart', () => {
223
+ this.isLoading = true;
224
+ this.error = null;
225
+ this.updateUI();
226
+ });
227
+ audio.addEventListener('canplay', () => {
228
+ this.isLoading = false;
229
+ this.updateUI();
230
+ });
231
+ }
232
+ handleEnded() {
233
+ this.isPlaying = false;
234
+ this.dispatchEvent(new CustomEvent('db-ended', { bubbles: true, composed: true }));
235
+ const playlist = this.getPlaylist();
236
+ if (playlist.length > 0) {
237
+ if (this.currentRepeatMode === 'one') {
238
+ if (this.audioEl) {
239
+ this.audioEl.currentTime = 0;
240
+ void this.audioEl.play();
241
+ }
242
+ }
243
+ else if (this.currentRepeatMode === 'all' || this.playlistIndex < playlist.length - 1) {
244
+ const nextIndex = this.playlistIndex + 1;
245
+ if (nextIndex < playlist.length) {
246
+ this.setPlaylistIndex(nextIndex);
247
+ }
248
+ else if (this.currentRepeatMode === 'all') {
249
+ this.setPlaylistIndex(0);
250
+ }
251
+ }
252
+ }
253
+ else if (this.getBool('loop') && this.audioEl) {
254
+ this.audioEl.currentTime = 0;
255
+ void this.audioEl.play();
256
+ }
257
+ this.updateUI();
258
+ }
259
+ setPlaylistIndex(index) {
260
+ this.playlistIndex = index;
261
+ this.dispatchEvent(new CustomEvent('db-playlist-change', {
262
+ detail: { index },
263
+ bubbles: true,
264
+ composed: true,
265
+ }));
266
+ this.loadCurrentTrack();
267
+ if (this.audioEl)
268
+ void this.audioEl.play();
269
+ }
270
+ togglePlay() {
271
+ if (!this.audioEl)
272
+ return;
273
+ if (this.isPlaying)
274
+ this.audioEl.pause();
275
+ else
276
+ void this.audioEl.play();
277
+ }
278
+ toggleMute() {
279
+ if (!this.audioEl)
280
+ return;
281
+ this.audioEl.muted = !this.isMuted;
282
+ }
283
+ toggleRepeatMode() {
284
+ const modes = ['none', 'one', 'all'];
285
+ const next = modes[(modes.indexOf(this.currentRepeatMode) + 1) % modes.length];
286
+ this.currentRepeatMode = next;
287
+ this.dispatchEvent(new CustomEvent('db-repeat-mode-change', {
288
+ detail: { mode: next },
289
+ bubbles: true,
290
+ composed: true,
291
+ }));
292
+ this.updateUI();
293
+ }
294
+ toggleShuffle() {
295
+ this.isShuffle = !this.isShuffle;
296
+ this.refreshShuffledPlaylist();
297
+ this.dispatchEvent(new CustomEvent('db-shuffle-change', {
298
+ detail: { shuffle: this.isShuffle },
299
+ bubbles: true,
300
+ composed: true,
301
+ }));
302
+ this.updateUI();
303
+ }
304
+ handlePlaybackRateChange(rate) {
305
+ if (!this.audioEl)
306
+ return;
307
+ this.audioEl.playbackRate = rate;
308
+ this.playbackRate = rate;
309
+ this.dispatchEvent(new CustomEvent('db-playback-rate-change', {
310
+ detail: { rate },
311
+ bubbles: true,
312
+ composed: true,
313
+ }));
314
+ }
315
+ handleKeyDown(event) {
316
+ if (!this.audioEl || !this.getBool('enable-keyboard', true))
317
+ return;
318
+ switch (event.code) {
319
+ case 'Space':
320
+ event.preventDefault();
321
+ this.togglePlay();
322
+ break;
323
+ case 'ArrowLeft':
324
+ event.preventDefault();
325
+ this.audioEl.currentTime = Math.max(0, this.currentTime - 10);
326
+ break;
327
+ case 'ArrowRight':
328
+ event.preventDefault();
329
+ this.audioEl.currentTime = Math.min(this.duration, this.currentTime + 10);
330
+ break;
331
+ case 'ArrowUp':
332
+ event.preventDefault();
333
+ this.audioEl.volume = Math.min(1, this.volume + 0.1);
334
+ this.audioEl.muted = false;
335
+ break;
336
+ case 'ArrowDown':
337
+ event.preventDefault();
338
+ this.audioEl.volume = Math.max(0, this.volume - 0.1);
339
+ this.audioEl.muted = this.audioEl.volume === 0;
340
+ break;
341
+ case 'KeyM':
342
+ event.preventDefault();
343
+ this.toggleMute();
344
+ break;
345
+ case 'KeyR':
346
+ event.preventDefault();
347
+ this.toggleRepeatMode();
348
+ break;
349
+ case 'KeyS':
350
+ event.preventDefault();
351
+ this.toggleShuffle();
352
+ break;
353
+ }
354
+ }
355
+ iconButton(icon, onClick, options) {
356
+ const button = document.createElement('button');
357
+ button.type = 'button';
358
+ button.className = `${CLASS}__control-button`;
359
+ button.append(createDbIcon(icon, this.getIconSize()));
360
+ if (options?.disabled)
361
+ button.disabled = true;
362
+ if (options?.data) {
363
+ Object.entries(options.data).forEach(([key, value]) => button.setAttribute(`data-${key}`, value));
364
+ }
365
+ button.addEventListener('click', onClick);
366
+ return button;
367
+ }
368
+ build() {
369
+ const size = this.getAttribute('size') || 'm';
370
+ const variant = this.getAttribute('variant') || 'default';
371
+ const theme = this.getAttribute('theme') || 'auto';
372
+ this.className = [
373
+ CLASS,
374
+ `${CLASS}--size-${size}`,
375
+ `${CLASS}--variant-${variant}`,
376
+ `${CLASS}--theme-${theme}`,
377
+ ].join(' ');
378
+ this.audioEl = document.createElement('audio');
379
+ this.audioEl.preload = 'metadata';
380
+ this.audioEl.hidden = true;
381
+ const loading = document.createElement('div');
382
+ loading.className = `${CLASS}__loading`;
383
+ const spinner = document.createElement('div');
384
+ spinner.className = `${CLASS}__spinner`;
385
+ loading.append(spinner);
386
+ const errorWrap = document.createElement('div');
387
+ errorWrap.className = `${CLASS}__error`;
388
+ const errorText = document.createElement('p');
389
+ errorText.textContent = '오디오를 재생할 수 없습니다.';
390
+ const retry = document.createElement('button');
391
+ retry.type = 'button';
392
+ retry.textContent = '다시 시도';
393
+ retry.addEventListener('click', () => window.location.reload());
394
+ errorWrap.append(errorText, retry);
395
+ const albumArt = document.createElement('div');
396
+ albumArt.className = `${CLASS}__album-art`;
397
+ this.albumImgEl = document.createElement('img');
398
+ this.albumImgEl.addEventListener('error', () => {
399
+ if (this.albumImgEl)
400
+ this.albumImgEl.style.display = 'none';
401
+ this.albumPlaceholderEl?.classList.remove('album-placeholder');
402
+ });
403
+ this.albumPlaceholderEl = document.createElement('div');
404
+ this.albumPlaceholderEl.className = 'album-placeholder';
405
+ this.albumPlaceholderEl.textContent = '🎵';
406
+ albumArt.append(this.albumImgEl, this.albumPlaceholderEl);
407
+ const main = document.createElement('div');
408
+ main.className = `${CLASS}__main`;
409
+ const info = document.createElement('div');
410
+ info.className = `${CLASS}__info`;
411
+ this.titleEl = document.createElement('div');
412
+ this.titleEl.className = `${CLASS}__title`;
413
+ this.artistEl = document.createElement('div');
414
+ this.artistEl.className = `${CLASS}__artist`;
415
+ this.albumEl = document.createElement('div');
416
+ this.albumEl.className = `${CLASS}__album`;
417
+ info.append(this.titleEl, this.artistEl, this.albumEl);
418
+ this.progressEl = document.createElement('div');
419
+ this.progressEl.className = `${CLASS}__progress`;
420
+ this.progressBarEl = document.createElement('div');
421
+ this.progressBarEl.className = `${CLASS}__progress-bar`;
422
+ this.progressHandleEl = document.createElement('div');
423
+ this.progressHandleEl.className = `${CLASS}__progress-handle`;
424
+ this.progressEl.append(this.progressBarEl, this.progressHandleEl);
425
+ this.progressEl.addEventListener('click', (event) => {
426
+ if (!this.audioEl || !this.progressEl)
427
+ return;
428
+ const rect = this.progressEl.getBoundingClientRect();
429
+ const percentage = (event.clientX - rect.left) / rect.width;
430
+ this.audioEl.currentTime = percentage * (this.duration || 0);
431
+ });
432
+ const controls = document.createElement('div');
433
+ controls.className = `${CLASS}__controls`;
434
+ const leftControls = document.createElement('div');
435
+ leftControls.className = `${CLASS}__left-controls`;
436
+ const centerControls = document.createElement('div');
437
+ centerControls.className = `${CLASS}__center-controls`;
438
+ const rightControls = document.createElement('div');
439
+ rightControls.className = `${CLASS}__right-controls`;
440
+ this.playButtonEl = document.createElement('button');
441
+ this.playButtonEl.type = 'button';
442
+ this.playButtonEl.className = `${CLASS}__play-button`;
443
+ this.playButtonEl.addEventListener('click', () => this.togglePlay());
444
+ centerControls.append(this.playButtonEl);
445
+ controls.append(leftControls, centerControls, rightControls);
446
+ const bottomControls = document.createElement('div');
447
+ bottomControls.className = `${CLASS}__bottom-controls`;
448
+ const leftBottom = document.createElement('div');
449
+ leftBottom.className = `${CLASS}__left-bottom`;
450
+ const rightBottom = document.createElement('div');
451
+ rightBottom.className = `${CLASS}__right-bottom`;
452
+ const time = document.createElement('div');
453
+ time.className = `${CLASS}__time`;
454
+ this.timeCurrentEl = document.createElement('span');
455
+ const slash = document.createElement('span');
456
+ slash.textContent = '/';
457
+ this.timeDurationEl = document.createElement('span');
458
+ time.append(this.timeCurrentEl, slash, this.timeDurationEl);
459
+ leftBottom.append(time);
460
+ const volumeWrap = document.createElement('div');
461
+ volumeWrap.className = `${CLASS}__volume`;
462
+ const muteButton = this.iconButton(this.isMuted || this.volume === 0 ? 'mute-filled' : 'volume-up', () => this.toggleMute());
463
+ muteButton.title = this.isMuted || this.volume === 0 ? '음소거 해제' : '음소거';
464
+ const volumeSlider = document.createElement('div');
465
+ volumeSlider.className = `${CLASS}__volume-slider`;
466
+ const volumeLabel = document.createElement('div');
467
+ volumeLabel.className = 'volume-label';
468
+ const volumeEmoji = document.createElement('span');
469
+ volumeEmoji.textContent = '🔊';
470
+ const volumePercent = document.createElement('span');
471
+ volumeLabel.append(volumeEmoji, volumePercent);
472
+ this.volumeInputEl = document.createElement('input');
473
+ this.volumeInputEl.type = 'range';
474
+ this.volumeInputEl.min = '0';
475
+ this.volumeInputEl.max = '1';
476
+ this.volumeInputEl.step = '0.1';
477
+ this.volumeInputEl.addEventListener('input', () => {
478
+ if (!this.audioEl || !this.volumeInputEl)
479
+ return;
480
+ const newVolume = parseFloat(this.volumeInputEl.value);
481
+ this.audioEl.volume = newVolume;
482
+ this.audioEl.muted = newVolume === 0;
483
+ });
484
+ volumeSlider.append(volumeLabel, this.volumeInputEl);
485
+ volumeWrap.append(muteButton, volumeSlider);
486
+ const settings = document.createElement('div');
487
+ settings.className = `${CLASS}__settings`;
488
+ const settingsButton = this.iconButton('settings', () => {
489
+ this.showSettingsMenu = !this.showSettingsMenu;
490
+ this.updateSettingsMenu();
491
+ });
492
+ this.settingsMenuEl = document.createElement('div');
493
+ this.settingsMenuEl.className = `${CLASS}__settings-menu`;
494
+ this.settingsMenuEl.hidden = true;
495
+ const settingGroup = document.createElement('div');
496
+ settingGroup.className = `${CLASS}__setting-group`;
497
+ const label = document.createElement('label');
498
+ label.textContent = '재생 속도';
499
+ const select = document.createElement('select');
500
+ select.addEventListener('change', () => this.handlePlaybackRateChange(parseFloat(select.value)));
501
+ settingGroup.append(label, select);
502
+ this.settingsMenuEl.append(settingGroup);
503
+ settings.append(settingsButton, this.settingsMenuEl);
504
+ rightBottom.append(volumeWrap, settings);
505
+ bottomControls.append(leftBottom, rightBottom);
506
+ main.append(info, this.progressEl, controls, bottomControls);
507
+ const playlist = document.createElement('div');
508
+ playlist.className = `${CLASS}__playlist`;
509
+ playlist.hidden = true;
510
+ const playlistTitle = document.createElement('h4');
511
+ playlistTitle.textContent = '플레이리스트';
512
+ this.playlistTracksEl = document.createElement('div');
513
+ this.playlistTracksEl.className = `${CLASS}__playlist-tracks`;
514
+ playlist.append(playlistTitle, this.playlistTracksEl);
515
+ this.append(this.audioEl, loading, errorWrap, albumArt, main, playlist);
516
+ this._leftControls = leftControls;
517
+ this._rightControls = rightControls;
518
+ this._controls = controls;
519
+ this._time = time;
520
+ this._volumeWrap = volumeWrap;
521
+ this._settings = settings;
522
+ this._playlist = playlist;
523
+ this._rateSelect = select;
524
+ this._volumePercent = volumePercent;
525
+ this._muteButton = muteButton;
526
+ }
527
+ get leftControls() {
528
+ return this._leftControls;
529
+ }
530
+ get rightControls() {
531
+ return this._rightControls;
532
+ }
533
+ get controlsEl() {
534
+ return this._controls;
535
+ }
536
+ get timeEl() {
537
+ return this._time;
538
+ }
539
+ get volumeWrap() {
540
+ return this._volumeWrap;
541
+ }
542
+ get settingsEl() {
543
+ return this._settings;
544
+ }
545
+ get playlistEl() {
546
+ return this._playlist;
547
+ }
548
+ get rateSelect() {
549
+ return this._rateSelect;
550
+ }
551
+ get volumePercent() {
552
+ return this._volumePercent;
553
+ }
554
+ get muteButton() {
555
+ return this._muteButton;
556
+ }
557
+ updateTrackInfo() {
558
+ const track = this.getCurrentTrack();
559
+ if (this.titleEl)
560
+ this.titleEl.textContent = track?.title || 'Unknown Track';
561
+ if (this.artistEl)
562
+ this.artistEl.textContent = track?.artist || 'Unknown Artist';
563
+ if (this.albumEl) {
564
+ if (track?.album) {
565
+ this.albumEl.textContent = track.album;
566
+ this.albumEl.hidden = false;
567
+ }
568
+ else {
569
+ this.albumEl.hidden = true;
570
+ }
571
+ }
572
+ if (this.albumImgEl && this.albumPlaceholderEl) {
573
+ if (track?.albumArt) {
574
+ this.albumImgEl.src = track.albumArt;
575
+ this.albumImgEl.alt = track.title || 'Album Art';
576
+ this.albumImgEl.style.display = '';
577
+ }
578
+ else {
579
+ this.albumImgEl.removeAttribute('src');
580
+ this.albumImgEl.style.display = 'none';
581
+ }
582
+ this.albumPlaceholderEl.className = 'album-placeholder';
583
+ }
584
+ }
585
+ updateProgress() {
586
+ const ratio = this.duration > 0 ? (this.currentTime / this.duration) * 100 : 0;
587
+ if (this.progressBarEl)
588
+ this.progressBarEl.style.width = `${ratio}%`;
589
+ if (this.progressHandleEl) {
590
+ this.progressHandleEl.style.left = `${ratio}%`;
591
+ if (ratio <= 0) {
592
+ this.progressHandleEl.style.transform = 'translate(0, -50%)';
593
+ }
594
+ else if (ratio >= 100) {
595
+ this.progressHandleEl.style.transform = 'translate(-100%, -50%)';
596
+ }
597
+ else {
598
+ this.progressHandleEl.style.transform = 'translate(-50%, -50%)';
599
+ }
600
+ }
601
+ }
602
+ updateTime() {
603
+ if (this.timeCurrentEl)
604
+ this.timeCurrentEl.textContent = this.formatTime(this.currentTime);
605
+ if (this.timeDurationEl)
606
+ this.timeDurationEl.textContent = this.formatTime(this.duration);
607
+ }
608
+ updateVolumeUI() {
609
+ if (this.volumeInputEl)
610
+ this.volumeInputEl.value = String(this.volume);
611
+ if (this.volumePercent)
612
+ this.volumePercent.textContent = `${Math.round(this.volume * 100)}%`;
613
+ if (this.muteButton) {
614
+ this.muteButton.replaceChildren(createDbIcon(this.isMuted || this.volume === 0 ? 'mute-filled' : 'volume-up', this.getIconSize()));
615
+ this.muteButton.title = this.isMuted || this.volume === 0 ? '음소거 해제' : '음소거';
616
+ }
617
+ }
618
+ updateSettingsMenu() {
619
+ if (!this.settingsMenuEl)
620
+ return;
621
+ this.settingsMenuEl.hidden = !this.showSettingsMenu;
622
+ }
623
+ rebuildControlButtons() {
624
+ const playlist = this.getPlaylist();
625
+ const iconSize = this.getIconSize() * 1.2;
626
+ this.leftControls.replaceChildren();
627
+ this.rightControls.replaceChildren();
628
+ if (playlist.length > 0) {
629
+ this.leftControls.append(this.iconButton('refresh', () => this.toggleShuffle(), { data: { shuffle: 'true' } }));
630
+ }
631
+ this.leftControls.append(this.iconButton('chevron-left', () => {
632
+ if (playlist.length > 0) {
633
+ const prev = this.playlistIndex - 1;
634
+ if (prev >= 0)
635
+ this.setPlaylistIndex(prev);
636
+ }
637
+ else if (this.audioEl) {
638
+ this.audioEl.currentTime = 0;
639
+ }
640
+ }, { disabled: playlist.length > 0 && this.playlistIndex === 0 }));
641
+ if (this.playButtonEl) {
642
+ this.playButtonEl.replaceChildren(createDbIcon(this.isPlaying ? 'pause' : 'play', iconSize));
643
+ }
644
+ this.rightControls.append(this.iconButton('chevron-right', () => {
645
+ if (playlist.length > 0) {
646
+ const next = this.playlistIndex + 1;
647
+ if (next < playlist.length)
648
+ this.setPlaylistIndex(next);
649
+ }
650
+ }, { disabled: playlist.length > 0 && this.playlistIndex === playlist.length - 1 }));
651
+ if (playlist.length > 0) {
652
+ this.rightControls.append(this.iconButton('repeat', () => this.toggleRepeatMode(), { data: { repeat: 'true' } }));
653
+ }
654
+ }
655
+ rebuildPlaylist() {
656
+ if (!this.playlistTracksEl)
657
+ return;
658
+ const playlist = this.getPlaylist();
659
+ const variant = this.getAttribute('variant') || 'default';
660
+ this.playlistEl.hidden = !(variant === 'full' && playlist.length > 0);
661
+ this.playlistTracksEl.replaceChildren();
662
+ playlist.forEach((track, index) => {
663
+ const row = document.createElement('div');
664
+ row.className = `${CLASS}__playlist-track${index === this.playlistIndex ? ` ${CLASS}__playlist-track--active` : ''}`;
665
+ row.addEventListener('click', () => this.setPlaylistIndex(index));
666
+ if (track.albumArt) {
667
+ const img = document.createElement('img');
668
+ img.src = track.albumArt;
669
+ img.alt = track.title || 'Album Art';
670
+ row.append(img);
671
+ }
672
+ const info = document.createElement('div');
673
+ info.className = `${CLASS}__playlist-track-info`;
674
+ const title = document.createElement('div');
675
+ title.className = `${CLASS}__playlist-track-title`;
676
+ title.textContent = track.title || 'Unknown Track';
677
+ const artist = document.createElement('div');
678
+ artist.className = `${CLASS}__playlist-track-artist`;
679
+ artist.textContent = track.artist || 'Unknown Artist';
680
+ info.append(title, artist);
681
+ row.append(info);
682
+ this.playlistTracksEl?.append(row);
683
+ });
684
+ }
685
+ updatePlaylistActive() {
686
+ if (!this.playlistTracksEl)
687
+ return;
688
+ Array.from(this.playlistTracksEl.children).forEach((child, index) => {
689
+ child.classList.toggle(`${CLASS}__playlist-track--active`, index === this.playlistIndex);
690
+ });
691
+ }
692
+ updateUI() {
693
+ const size = this.getAttribute('size') || 'm';
694
+ const variant = this.getAttribute('variant') || 'default';
695
+ const theme = this.getAttribute('theme') || 'auto';
696
+ const showControls = this.getBool('show-controls', true);
697
+ const showProgress = this.getBool('show-progress', true);
698
+ const showTime = this.getBool('show-time', true);
699
+ const showVolume = this.getBool('show-volume', true);
700
+ const showSettings = this.getBool('show-settings', false);
701
+ this.className = [
702
+ CLASS,
703
+ `${CLASS}--size-${size}`,
704
+ `${CLASS}--variant-${variant}`,
705
+ `${CLASS}--theme-${theme}`,
706
+ this.isPlaying && `${CLASS}--playing`,
707
+ !this.isPlaying && `${CLASS}--paused`,
708
+ this.isMuted && `${CLASS}--muted`,
709
+ this.isLoading && `${CLASS}--loading`,
710
+ this.error && `${CLASS}--error`,
711
+ this.isShuffle && `${CLASS}--shuffle`,
712
+ `${CLASS}--repeat-${this.currentRepeatMode}`,
713
+ ]
714
+ .filter(Boolean)
715
+ .join(' ');
716
+ if (this.audioEl) {
717
+ this.audioEl.autoplay = this.getBool('auto-play');
718
+ this.audioEl.loop = this.getBool('loop');
719
+ this.audioEl.muted = this.getBool('muted');
720
+ this.audioEl.playbackRate = this.playbackRate;
721
+ }
722
+ if (this.progressEl)
723
+ this.progressEl.hidden = !showProgress;
724
+ if (this.controlsEl)
725
+ this.controlsEl.hidden = !showControls;
726
+ if (this.timeEl)
727
+ this.timeEl.hidden = !showTime;
728
+ if (this.volumeWrap)
729
+ this.volumeWrap.hidden = !showVolume;
730
+ if (this.settingsEl)
731
+ this.settingsEl.hidden = !showSettings;
732
+ this.rateSelect.replaceChildren(...this.getPlaybackRates().map((rate) => {
733
+ const option = document.createElement('option');
734
+ option.value = String(rate);
735
+ option.textContent = `${rate}x`;
736
+ if (rate === this.playbackRate)
737
+ option.selected = true;
738
+ return option;
739
+ }));
740
+ this.rebuildControlButtons();
741
+ this.updateTrackInfo();
742
+ this.updateProgress();
743
+ this.updateTime();
744
+ this.updateVolumeUI();
745
+ this.updateSettingsMenu();
746
+ this.rebuildPlaylist();
747
+ }
748
+ }
749
+ if (!customElements.get('db-audio-player'))
750
+ customElements.define('db-audio-player', DbAudioPlayer);
751
+
752
+ export { DbAudioPlayer };