@designbasekorea/ui-wc 0.8.13 → 0.9.2

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 (249) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE-PAYMENT-LOGOS.md +437 -0
  3. package/README.md +67 -3
  4. package/dist/esm/components/date-picker-utils.js +1 -1
  5. package/dist/esm/components/db-accordion.js +1 -1
  6. package/dist/esm/components/db-achievement-list.d.ts +31 -0
  7. package/dist/esm/components/db-achievement-list.js +1 -0
  8. package/dist/esm/components/db-achievement-modal.d.ts +38 -0
  9. package/dist/esm/components/db-achievement-modal.js +1 -0
  10. package/dist/esm/components/db-activity-heatmap.js +1 -1
  11. package/dist/esm/components/db-ad-banner.js +1 -1
  12. package/dist/esm/components/db-ai-chain-of-thought.d.ts +14 -0
  13. package/dist/esm/components/db-ai-chain-of-thought.js +1 -0
  14. package/dist/esm/components/db-ai-chat.js +1 -1
  15. package/dist/esm/components/db-ai-checkpoint.d.ts +14 -0
  16. package/dist/esm/components/db-ai-checkpoint.js +1 -0
  17. package/dist/esm/components/db-ai-confirmation.d.ts +14 -0
  18. package/dist/esm/components/db-ai-confirmation.js +1 -0
  19. package/dist/esm/components/db-ai-context.d.ts +15 -0
  20. package/dist/esm/components/db-ai-context.js +1 -0
  21. package/dist/esm/components/db-ai-conversation.d.ts +21 -0
  22. package/dist/esm/components/db-ai-conversation.js +1 -0
  23. package/dist/esm/components/db-ai-execution-quote.d.ts +41 -0
  24. package/dist/esm/components/db-ai-execution-quote.js +1 -0
  25. package/dist/esm/components/db-ai-file-tree.d.ts +14 -0
  26. package/dist/esm/components/db-ai-file-tree.js +1 -0
  27. package/dist/esm/components/db-ai-glow.d.ts +1 -1
  28. package/dist/esm/components/db-ai-inline-citation.d.ts +16 -0
  29. package/dist/esm/components/db-ai-inline-citation.js +1 -0
  30. package/dist/esm/components/db-ai-message.d.ts +19 -0
  31. package/dist/esm/components/db-ai-message.js +1 -0
  32. package/dist/esm/components/db-ai-model-select.d.ts +36 -0
  33. package/dist/esm/components/db-ai-model-select.js +1 -0
  34. package/dist/esm/components/db-ai-plan.d.ts +14 -0
  35. package/dist/esm/components/db-ai-plan.js +1 -0
  36. package/dist/esm/components/db-ai-queue.d.ts +14 -0
  37. package/dist/esm/components/db-ai-queue.js +1 -0
  38. package/dist/esm/components/db-ai-reasoning.d.ts +14 -0
  39. package/dist/esm/components/db-ai-reasoning.js +1 -0
  40. package/dist/esm/components/db-ai-sources.d.ts +14 -0
  41. package/dist/esm/components/db-ai-sources.js +1 -0
  42. package/dist/esm/components/db-ai-stack-trace.d.ts +14 -0
  43. package/dist/esm/components/db-ai-stack-trace.js +1 -0
  44. package/dist/esm/components/db-ai-subagent.d.ts +14 -0
  45. package/dist/esm/components/db-ai-subagent.js +1 -0
  46. package/dist/esm/components/db-ai-task-status.d.ts +26 -0
  47. package/dist/esm/components/db-ai-task-status.js +1 -0
  48. package/dist/esm/components/db-ai-task.d.ts +14 -0
  49. package/dist/esm/components/db-ai-task.js +1 -0
  50. package/dist/esm/components/db-ai-terminal.d.ts +14 -0
  51. package/dist/esm/components/db-ai-terminal.js +1 -0
  52. package/dist/esm/components/db-ai-test-results.d.ts +14 -0
  53. package/dist/esm/components/db-ai-test-results.js +1 -0
  54. package/dist/esm/components/db-ai-tool.d.ts +14 -0
  55. package/dist/esm/components/db-ai-tool.js +1 -0
  56. package/dist/esm/components/db-alert.js +1 -1
  57. package/dist/esm/components/db-app-badge.js +1 -1
  58. package/dist/esm/components/db-attachment-list.d.ts +38 -0
  59. package/dist/esm/components/db-attachment-list.js +1 -0
  60. package/dist/esm/components/db-audio-player.js +1 -1
  61. package/dist/esm/components/db-bento-card.d.ts +55 -0
  62. package/dist/esm/components/db-bento-card.js +1 -0
  63. package/dist/esm/components/db-bento-grid.d.ts +25 -0
  64. package/dist/esm/components/db-bento-grid.js +1 -0
  65. package/dist/esm/components/db-boarding-pass-stack.d.ts +49 -0
  66. package/dist/esm/components/db-boarding-pass-stack.js +1 -0
  67. package/dist/esm/components/db-boarding-pass.d.ts +32 -0
  68. package/dist/esm/components/db-boarding-pass.js +1 -0
  69. package/dist/esm/components/db-border-beam.d.ts +52 -0
  70. package/dist/esm/components/db-border-beam.js +1 -0
  71. package/dist/esm/components/db-bottom-navigation.js +1 -1
  72. package/dist/esm/components/db-bottom-sheet.js +1 -1
  73. package/dist/esm/components/db-breadcrumbs.js +1 -1
  74. package/dist/esm/components/db-button.d.ts +1 -1
  75. package/dist/esm/components/db-button.js +1 -1
  76. package/dist/esm/components/db-calendar.js +1 -1
  77. package/dist/esm/components/db-card.js +1 -1
  78. package/dist/esm/components/db-carousel.d.ts +3 -3
  79. package/dist/esm/components/db-carousel.js +1 -1
  80. package/dist/esm/components/db-chat-composer.d.ts +2 -0
  81. package/dist/esm/components/db-chat-composer.js +1 -1
  82. package/dist/esm/components/db-chat-message.d.ts +1 -1
  83. package/dist/esm/components/db-chat-orb.d.ts +1 -1
  84. package/dist/esm/components/db-chat-room.js +1 -1
  85. package/dist/esm/components/db-chat-widget.d.ts +1 -1
  86. package/dist/esm/components/db-chat-widget.js +1 -1
  87. package/dist/esm/components/db-chip.js +1 -1
  88. package/dist/esm/components/db-code-block.js +1 -1
  89. package/dist/esm/components/db-color-picker.d.ts +1 -0
  90. package/dist/esm/components/db-color-picker.js +1 -1
  91. package/dist/esm/components/db-confetti.d.ts +90 -0
  92. package/dist/esm/components/db-confetti.js +1 -0
  93. package/dist/esm/components/db-context-bar.d.ts +13 -0
  94. package/dist/esm/components/db-context-bar.js +1 -0
  95. package/dist/esm/components/db-credit-balance.d.ts +17 -0
  96. package/dist/esm/components/db-credit-balance.js +1 -0
  97. package/dist/esm/components/db-credit-card.d.ts +51 -0
  98. package/dist/esm/components/db-credit-card.js +1 -0
  99. package/dist/esm/components/db-credit-dropdown.d.ts +31 -0
  100. package/dist/esm/components/db-credit-dropdown.js +1 -0
  101. package/dist/esm/components/db-credit-history.d.ts +31 -0
  102. package/dist/esm/components/db-credit-history.js +1 -0
  103. package/dist/esm/components/db-credit-recharge-modal.d.ts +35 -0
  104. package/dist/esm/components/db-credit-recharge-modal.js +1 -0
  105. package/dist/esm/components/db-credit-recharge.d.ts +39 -0
  106. package/dist/esm/components/db-credit-recharge.js +1 -0
  107. package/dist/esm/components/db-date-picker.js +1 -1
  108. package/dist/esm/components/db-design-canvas.d.ts +137 -0
  109. package/dist/esm/components/db-design-canvas.js +1 -0
  110. package/dist/esm/components/db-design-toolbar.d.ts +60 -0
  111. package/dist/esm/components/db-design-toolbar.js +1 -0
  112. package/dist/esm/components/db-device-mockup.d.ts +100 -0
  113. package/dist/esm/components/db-device-mockup.js +1 -0
  114. package/dist/esm/components/db-download-progress.d.ts +1 -1
  115. package/dist/esm/components/db-download-progress.js +1 -1
  116. package/dist/esm/components/db-drawer.js +1 -1
  117. package/dist/esm/components/db-dropdown.d.ts +2 -0
  118. package/dist/esm/components/db-dropdown.js +1 -1
  119. package/dist/esm/components/db-dropzone.js +1 -1
  120. package/dist/esm/components/db-email-verify.d.ts +1 -7
  121. package/dist/esm/components/db-email-verify.js +1 -1
  122. package/dist/esm/components/db-file-uploader.d.ts +10 -0
  123. package/dist/esm/components/db-file-uploader.js +1 -1
  124. package/dist/esm/components/db-filter-bar.d.ts +34 -0
  125. package/dist/esm/components/db-filter-bar.js +1 -0
  126. package/dist/esm/components/db-flight-status.d.ts +33 -0
  127. package/dist/esm/components/db-flight-status.js +1 -0
  128. package/dist/esm/components/db-floating-action-button.d.ts +24 -14
  129. package/dist/esm/components/db-floating-action-button.js +1 -1
  130. package/dist/esm/components/db-footer.d.ts +3 -0
  131. package/dist/esm/components/db-footer.js +1 -1
  132. package/dist/esm/components/db-gantt.js +1 -1
  133. package/dist/esm/components/db-glyph-matrix.d.ts +58 -0
  134. package/dist/esm/components/db-glyph-matrix.js +1 -0
  135. package/dist/esm/components/db-icons.d.ts +1 -1
  136. package/dist/esm/components/db-icons.js +1 -1
  137. package/dist/esm/components/db-image-list.d.ts +2 -0
  138. package/dist/esm/components/db-image-list.js +1 -1
  139. package/dist/esm/components/db-indicator.js +1 -1
  140. package/dist/esm/components/db-input.js +1 -1
  141. package/dist/esm/components/db-kanban.js +1 -1
  142. package/dist/esm/components/db-lightbox.d.ts +4 -0
  143. package/dist/esm/components/db-lightbox.js +1 -1
  144. package/dist/esm/components/db-markdown-editor.js +1 -1
  145. package/dist/esm/components/db-marquee.d.ts +2 -10
  146. package/dist/esm/components/db-marquee.js +1 -1
  147. package/dist/esm/components/db-menu-item.d.ts +2 -0
  148. package/dist/esm/components/db-menu-item.js +1 -1
  149. package/dist/esm/components/db-modal.d.ts +1 -1
  150. package/dist/esm/components/db-modal.js +1 -1
  151. package/dist/esm/components/db-navbar.d.ts +3 -1
  152. package/dist/esm/components/db-navbar.js +1 -1
  153. package/dist/esm/components/db-navigation-rail.d.ts +1 -1
  154. package/dist/esm/components/db-node.d.ts +83 -0
  155. package/dist/esm/components/db-node.js +1 -0
  156. package/dist/esm/components/db-notification-dropdown.d.ts +30 -0
  157. package/dist/esm/components/db-notification-dropdown.js +1 -0
  158. package/dist/esm/components/db-notification-inbox.d.ts +25 -0
  159. package/dist/esm/components/db-notification-inbox.js +1 -0
  160. package/dist/esm/components/db-onboarding-modal.js +1 -1
  161. package/dist/esm/components/db-otp-input.d.ts +1 -1
  162. package/dist/esm/components/db-otp-input.js +1 -1
  163. package/dist/esm/components/db-page-header.d.ts +8 -0
  164. package/dist/esm/components/db-page-header.js +1 -1
  165. package/dist/esm/components/db-pagination.js +1 -1
  166. package/dist/esm/components/db-phone-verify.d.ts +1 -1
  167. package/dist/esm/components/db-phone-verify.js +1 -1
  168. package/dist/esm/components/db-plan-overview.d.ts +26 -0
  169. package/dist/esm/components/db-plan-overview.js +1 -0
  170. package/dist/esm/components/db-popover.js +1 -1
  171. package/dist/esm/components/db-progress-step.js +1 -1
  172. package/dist/esm/components/db-progressive-blur.d.ts +35 -0
  173. package/dist/esm/components/db-progressive-blur.js +1 -0
  174. package/dist/esm/components/db-receipt.d.ts +37 -0
  175. package/dist/esm/components/db-receipt.js +1 -0
  176. package/dist/esm/components/db-resizable-panels.js +1 -1
  177. package/dist/esm/components/db-search-bar.js +1 -1
  178. package/dist/esm/components/db-section-testimonials.d.ts +1 -4
  179. package/dist/esm/components/db-section-testimonials.js +1 -1
  180. package/dist/esm/components/db-segment-control.d.ts +0 -4
  181. package/dist/esm/components/db-segment-control.js +1 -1
  182. package/dist/esm/components/db-select.js +1 -1
  183. package/dist/esm/components/db-share.js +1 -1
  184. package/dist/esm/components/db-sidebar.d.ts +14 -1
  185. package/dist/esm/components/db-sidebar.js +1 -1
  186. package/dist/esm/components/db-split-view.d.ts +2 -0
  187. package/dist/esm/components/db-split-view.js +1 -1
  188. package/dist/esm/components/db-stepper.d.ts +0 -3
  189. package/dist/esm/components/db-stepper.js +1 -1
  190. package/dist/esm/components/db-table.js +1 -1
  191. package/dist/esm/components/db-testimonial.js +1 -1
  192. package/dist/esm/components/db-textarea.js +1 -1
  193. package/dist/esm/components/db-ticket.d.ts +18 -0
  194. package/dist/esm/components/db-ticket.js +1 -0
  195. package/dist/esm/components/db-time-picker.d.ts +1 -1
  196. package/dist/esm/components/db-time-picker.js +1 -1
  197. package/dist/esm/components/db-tool-button.d.ts +57 -0
  198. package/dist/esm/components/db-tool-button.js +1 -0
  199. package/dist/esm/components/db-top-banner.js +1 -1
  200. package/dist/esm/components/db-tutorial.js +1 -1
  201. package/dist/esm/components/db-tweet-card.d.ts +101 -0
  202. package/dist/esm/components/db-tweet-card.js +1 -0
  203. package/dist/esm/components/db-two-factor.d.ts +5 -11
  204. package/dist/esm/components/db-two-factor.js +1 -1
  205. package/dist/esm/components/db-video-player.js +1 -1
  206. package/dist/esm/components/db-voice-control.d.ts +25 -0
  207. package/dist/esm/components/db-voice-control.js +1 -0
  208. package/dist/esm/components/db-workspace-invite-modal.d.ts +51 -0
  209. package/dist/esm/components/db-workspace-invite-modal.js +1 -0
  210. package/dist/esm/components/db-workspace-members.d.ts +36 -0
  211. package/dist/esm/components/db-workspace-members.js +1 -0
  212. package/dist/esm/components/db-youtube-player.js +1 -1
  213. package/dist/esm/components/device-mockup-style.js +1 -0
  214. package/dist/esm/components/flight-route.js +1 -0
  215. package/dist/esm/components/input-validation.js +1 -1
  216. package/dist/esm/components/node-style.js +1 -0
  217. package/dist/esm/components/paper-card.d.ts +26 -0
  218. package/dist/esm/components/paper-card.js +1 -0
  219. package/dist/esm/components/random-gradient-palettes.d.ts +4 -0
  220. package/dist/esm/core/ai-element.d.ts +47 -0
  221. package/dist/esm/core/ai-element.js +1 -0
  222. package/dist/esm/core/credit-ui.js +1 -0
  223. package/dist/esm/core/i18n-copy.js +1 -0
  224. package/dist/esm/core/i18n-en.js +1 -0
  225. package/dist/esm/core/i18n.d.ts +206 -1
  226. package/dist/esm/core/i18n.js +1 -1
  227. package/dist/esm/core/notification-ui.d.ts +17 -0
  228. package/dist/esm/core/notification-ui.js +1 -0
  229. package/dist/esm/helpers/payment-brands.d.ts +56 -0
  230. package/dist/esm/helpers/payment-brands.js +1 -0
  231. package/dist/esm/helpers/toast.d.ts +2 -3
  232. package/dist/esm/index.d.ts +63 -3
  233. package/dist/esm/index.js +1 -1
  234. package/dist/esm/node_modules/@designbasekorea/carousel/dist/chunks/chunk-AGE5BHQ4.js +1 -0
  235. package/dist/esm/node_modules/@designbasekorea/carousel/dist/chunks/chunk-GM4TCAP5.js +1 -0
  236. package/dist/esm/node_modules/@designbasekorea/carousel/dist/chunks/chunk-GSN4JZN7.js +1 -0
  237. package/dist/esm/node_modules/@designbasekorea/carousel/dist/chunks/chunk-VJJVQGV4.js +1 -0
  238. package/dist/esm/node_modules/@designbasekorea/carousel/dist/chunks/chunk-YWLMJGDH.js +1 -0
  239. package/dist/esm/node_modules/@designbasekorea/ui-contracts/dist/components.js +1 -1
  240. package/dist/esm/ui-contracts/dist/components.d.d.ts +76 -0
  241. package/dist/esm/ui-contracts/dist/primitives.d.d.ts +21 -0
  242. package/dist/index.cjs +1 -1
  243. package/dist/index.css +1 -1
  244. package/dist/index.d.ts +2223 -63
  245. package/dist/index.esm.js +1 -1
  246. package/dist/react/index.cjs +1 -1
  247. package/dist/react/index.d.ts +793 -6726
  248. package/dist/react/index.esm.js +1 -1
  249. package/package.json +16 -9
@@ -1 +1 @@
1
- import{createDbIcon as t,getDbComponentIconSize as e}from"./db-icons.js";import"./db-button.js";import{TIME_PICKER_FORMATS as i,TIME_PICKER_SIZES as s,TIME_PICKER_DROPDOWN_POSITIONS as n}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";import{coerceToContract as o}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";const r="designbase-wc-time-picker";class DbTimePicker extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.isOpen=!1,this.hours=12,this.minutes=0,this.seconds=0,this.period="PM",this.inputText="",this.tempHours=12,this.tempMinutes=0,this.tempSeconds=0,this.tempPeriod="PM",this.boundDocumentMouseDown=t=>this.handleDocumentMouseDown(t),this.boundViewportChange=()=>this.handleViewportChange(),this.viewportChangeFrame=null,this.handleDocumentKeyDown=t=>{"Escape"===t.key&&this.isOpen&&(t.preventDefault(),this.closePicker(),this.inputEl?.focus())},this.boundDocumentKeyDown=t=>this.handleDocumentKeyDown(t)}static get observedAttributes(){return["value","default-value","type","format","minute-step","size","disabled","readonly","label","full-width","error","dropdown-position","open"]}connectedCallback(){this.built||this.build(),this.parseTimeString(this.getAttribute("value")??this.getAttribute("default-value")??"12:00"),this.syncInputText(),this.render(),document.addEventListener("mousedown",this.boundDocumentMouseDown),document.addEventListener("keydown",this.boundDocumentKeyDown),window.addEventListener("resize",this.boundViewportChange),window.addEventListener("scroll",this.boundViewportChange,{capture:!0,passive:!0})}disconnectedCallback(){document.removeEventListener("mousedown",this.boundDocumentMouseDown),document.removeEventListener("keydown",this.boundDocumentKeyDown),window.removeEventListener("resize",this.boundViewportChange),window.removeEventListener("scroll",this.boundViewportChange,{capture:!0}),null!==this.viewportChangeFrame&&cancelAnimationFrame(this.viewportChangeFrame),this.viewportChangeFrame=null,this.dropdownEl?.remove()}attributeChangedCallback(t){if(this.built){if("value"===t){const t=this.getAttribute("value");t&&(this.parseTimeString(t),this.syncInputText())}if("open"===t)return this.isOpen=this.hasAttribute("open"),void this.render();this.render()}}get value(){return this.getAttribute("value")||""}set value(t){t?this.setAttribute("value",t):this.removeAttribute("value")}get format(){const t=this.getAttribute("format")||"24h";return i.includes(t)?t:"24h"}set format(t){this.setAttribute("format",t)}get type(){return"modal"===this.getAttribute("type")?"modal":"dropdown"}get is12h(){return"12h"===this.format||"12h-with-seconds"===this.format}get showSeconds(){return"12h-with-seconds"===this.format||"24h-with-seconds"===this.format}get minuteStep(){const t=Number(this.getAttribute("minute-step")||"1");return Number.isFinite(t)&&t>0?t:1}get normalizedSize(){return o(this.getAttribute("size"),s,"m")}get dropdownPosition(){return o(this.getAttribute("dropdown-position"),n,"auto")}get itemHeight(){return"s"===this.normalizedSize?32:"l"===this.normalizedSize?40:28}get disabled(){return this.hasAttribute("disabled")}set disabled(t){this.toggleAttribute("disabled",Boolean(t))}get readonly(){return this.hasAttribute("readonly")}set readonly(t){this.toggleAttribute("readonly",Boolean(t))}build(){this.built=!0,this.labelEl=document.createElement("label"),this.labelEl.className=`${r}__label`,this.triggerEl=document.createElement("div"),this.triggerEl.className=`${r}__trigger`;const t=document.createElement("button");t.type="button",t.className=`${r}__icon-display`,t.setAttribute("aria-label","시간 선택 열기"),t.addEventListener("click",()=>this.toggleOpen()),this.inputEl=document.createElement("input"),this.inputEl.type="text",this.inputEl.className=`${r}__input`,this.inputEl.setAttribute("aria-label","시간 입력"),this.inputEl.addEventListener("click",()=>this.openPicker()),this.inputEl.addEventListener("blur",()=>this.handleInputBlur()),this.inputEl.addEventListener("input",()=>{this.inputText=this.inputEl?.value||""});const e=document.createElement("button");e.type="button",e.className=`${r}__toggle`,e.setAttribute("aria-label","시간 선택 열기"),e.addEventListener("click",()=>this.toggleOpen()),this.triggerEl.append(t,this.inputEl,e),this.dropdownEl=document.createElement("div"),this.dropdownEl.className=`${r}__dropdown`,this.dropdownEl.setAttribute("role","dialog"),this.dropdownEl.setAttribute("aria-modal","false")}pad2(t){return String(t).padStart(2,"0")}to24Hour(t,e){return"AM"===e?12===t?0:t:12===t?12:t+12}formatTimeString(t,e,i,s){const n="AM"===(s||this.period)?"오전":"오후";return this.is12h?`${n} ${this.pad2(t)}:${this.pad2(e)}${this.showSeconds?`:${this.pad2(i)}`:""}`:`${this.pad2(t)}:${this.pad2(e)}${this.showSeconds?`:${this.pad2(i)}`:""}`}toValue24(t,e,i,s){const n=this.is12h?this.to24Hour(t,s||this.period):t;return`${this.pad2(n)}:${this.pad2(e)}${this.showSeconds?`:${this.pad2(i)}`:""}`}parseTimeString(t){const e=t.trim();if(this.is12h){const t=this.showSeconds?/^(오전|오후|AM|PM)?\s*(0?[1-9]|1[0-2]):([0-5]\d):([0-5]\d)\s*(AM|PM|오전|오후)?$/i:/^(오전|오후|AM|PM)?\s*(0?[1-9]|1[0-2]):([0-5]\d)\s*(AM|PM|오전|오후)?$/i,i=e.match(t);if(i){const t=i[1],e=this.showSeconds?i[5]:i[4],s=(t||e||"AM").toUpperCase();return this.hours=parseInt(i[2],10),this.minutes=parseInt(i[3],10),this.seconds=this.showSeconds?parseInt(i[4],10):0,void(this.period="오후"===s||"PM"===s?"PM":"AM")}}else{const t=this.showSeconds?/^([01]?\d|2[0-3]):([0-5]\d):([0-5]\d)$/:/^([01]?\d|2[0-3]):([0-5]\d)$/,i=e.match(t);i&&(this.hours=parseInt(i[1],10),this.minutes=parseInt(i[2],10),this.seconds=this.showSeconds?parseInt(i[3],10):0)}}syncInputText(){this.inputText=this.formatTimeString(this.hours,this.minutes,this.seconds),this.inputEl&&(this.inputEl.value=this.inputText)}emitChange(t){this.setAttribute("value",t),this.dispatchEvent(new CustomEvent("db-change",{detail:{value:t},bubbles:!0,composed:!0}))}handleTimeChange(t,e,i=this.seconds,s){this.hours=t,this.minutes=e,this.seconds=i,s&&(this.period=s);const n=this.formatTimeString(t,e,i,s);this.inputText=n,this.inputEl&&(this.inputEl.value=n),this.emitChange(this.toValue24(t,e,i,s))}handleInputBlur(){if(!this.inputEl)return;const t=this.inputText;this.parseTimeString(this.inputEl.value);this.formatTimeString(this.hours,this.minutes,this.seconds)!==t||this.inputEl.value.trim()?this.handleTimeChange(this.hours,this.minutes,this.seconds,this.period):this.syncInputText()}openPicker(){this.disabled||this.readonly||(this.isOpen=!0,"modal"===this.type&&(this.tempHours=this.hours,this.tempMinutes=this.minutes,this.tempSeconds=this.seconds,this.tempPeriod=this.period),this.render())}closePicker(){this.isOpen=!1,this.render()}toggleOpen(){this.isOpen?this.closePicker():this.openPicker()}handleDocumentMouseDown(t){if(!this.isOpen)return;const e=t.target;this.contains(e)||this.dropdownEl?.contains(e)||this.closePicker()}handleViewportChange(){this.isOpen&&"dropdown"===this.type&&null===this.viewportChangeFrame&&(this.viewportChangeFrame=requestAnimationFrame(()=>{this.viewportChangeFrame=null,this.isOpen&&"dropdown"===this.type&&this.positionDropdown()}))}positionDropdown(){if(!this.isOpen||"dropdown"!==this.type||!this.triggerEl||!this.dropdownEl||this.dropdownEl.hidden)return;const t=this.dropdownEl,e=this.triggerEl.getBoundingClientRect();t.style.width="";const i=t.getBoundingClientRect(),s=Math.max(0,window.innerWidth-16),n=Math.min(i.width,s);t.style.width=`${Math.round(n)}px`;const o=this.dropdownPosition,r=e.bottom+4+i.height<=window.innerHeight-8,h=e.top-4-i.height>=8,d="auto"===o?r||!h?"bottom":"top":o.startsWith("top")?"top":"bottom",a="auto"===o?e.left+n>window.innerWidth-8?"end":"start":o.endsWith("end")?"end":"start",l="end"===a?e.right-n:e.left,u=Math.max(8,Math.min(l,window.innerWidth-n-8)),p="top"===d?e.top-i.height-4:e.bottom+4,c=Math.max(8,Math.min(p,window.innerHeight-i.height-8));t.style.left=`${Math.round(u)}px`,t.style.top=`${Math.round(c)}px`,t.dataset.placement=`${d}-${a}`}createWheel(t){const{label:e,items:i,value:s,onChange:n,formatter:o}=t,h=document.createElement("div");h.className=`${r}__wheel-container`;const d=document.createElement("div");d.className=`${r}__wheel-label`,d.textContent=e,h.appendChild(d);const a=document.createElement("div");a.className=`${r}__wheel-viewport`;const l=document.createElement("div");l.className=`${r}__wheel`,l.setAttribute("role","listbox"),l.setAttribute("aria-label",e);let u=null,p=!1,c=!1,m=0,b=0;const indexOfValue=t=>i.findIndex(e=>e===t),scrollToIndex=(t,e=!0)=>{const i=t*this.itemHeight;p=!0,l.scrollTo({top:i,behavior:e?"smooth":"auto"}),window.setTimeout(()=>{p=!1},150)},fmt=t=>o?o(t):"number"==typeof t?this.pad2(t):String(t);i.forEach(t=>{const e=document.createElement("div");e.className=[`${r}__wheel-item`,t===s&&`${r}__wheel-item--selected`].filter(Boolean).join(" "),e.setAttribute("role","option"),e.setAttribute("aria-selected",String(t===s)),e.textContent=fmt(t),e.addEventListener("click",()=>{const e=indexOfValue(t);scrollToIndex(e,!0),t!==s&&n(t)}),l.appendChild(e)});const handleScroll=()=>{p||(u&&window.clearTimeout(u),u=window.setTimeout(()=>{const t=l.scrollTop,e=Math.round(t/this.itemHeight),o=Math.max(0,Math.min(i.length-1,e)),r=i[o];scrollToIndex(o,!0),r!==s&&n(r)},120))};l.addEventListener("scroll",handleScroll,{passive:!0}),l.addEventListener("pointerdown",t=>{c=!0,m=t.clientY,b=l.scrollTop,l.setPointerCapture(t.pointerId),l.style.cursor="grabbing"}),l.addEventListener("pointermove",t=>{c&&(l.scrollTop=b-(t.clientY-m))});const endDrag=t=>{c&&(c=!1,l.releasePointerCapture(t.pointerId),l.style.cursor="grab",handleScroll())};l.addEventListener("pointerup",endDrag),l.addEventListener("pointercancel",endDrag);const w=indexOfValue(s);w>=0&&requestAnimationFrame(()=>scrollToIndex(w,!1));const g=document.createElement("div");g.className=`${r}__wheel-center-indicator`,g.setAttribute("aria-hidden","true");const E=document.createElement("div");E.className=`${r}__wheel-fade wheel-fade--top`,E.setAttribute("aria-hidden","true");const v=document.createElement("div");return v.className=`${r}__wheel-fade wheel-fade--bottom`,v.setAttribute("aria-hidden","true"),a.append(l,g,E,v),h.appendChild(a),h}renderSelector(e=!1){const i=document.createElement("div");i.className=`${r}__selector`;const s=document.createElement("div");s.className=`${r}__picker-wheels`;const n=this.is12h?Array.from({length:12},(t,e)=>e+1):Array.from({length:24},(t,e)=>e),o=Array.from({length:Math.floor(60/this.minuteStep)},(t,e)=>e*this.minuteStep),h=Array.from({length:60},(t,e)=>e),d=e?this.tempHours:this.hours,a=e?this.tempMinutes:this.minutes,l=e?this.tempSeconds:this.seconds,u=e?this.tempPeriod:this.period,onChange=(t,i,s,n)=>{if(e)return this.tempHours=t,this.tempMinutes=i,this.tempSeconds=s,void(this.tempPeriod=n);this.handleTimeChange(t,i,s,n)};if(s.appendChild(this.createWheel({label:"시간",items:n,value:d,onChange:t=>onChange(t,a,l,u)})),s.appendChild(this.createWheel({label:"분",items:o,value:o.includes(a)?a:o[0],onChange:t=>onChange(d,t,l,u)})),this.showSeconds&&s.appendChild(this.createWheel({label:"초",items:h,value:l,onChange:t=>onChange(d,a,t,u)})),this.is12h&&s.appendChild(this.createWheel({label:"오전/오후",items:["AM","PM"],value:u,onChange:t=>onChange(d,a,l,t),formatter:t=>"AM"===t?"오전":"오후"})),i.appendChild(s),e)return i;const p=document.createElement("div");p.className=`${r}__dropdown-actions`;const c=document.createElement("div");c.className=`${r}__preview`,c.setAttribute("aria-live","polite"),c.appendChild(t("clock",16));const m=document.createElement("div");m.className=`${r}__preview-time`,m.textContent=this.formatTimeString(d,a,l,u),c.appendChild(m);const b=document.createElement("db-button");return b.setAttribute("variant","primary"),b.setAttribute("size","m"),b.textContent="선택",b.addEventListener("click",()=>{this.handleTimeChange(d,a,l,u),this.closePicker()}),p.append(c,b),i.appendChild(p),i}render(){const i=this.normalizedSize,s=this.dropdownPosition,n=e(i);if(this.className=[r,`${r}--${i}`,this.hasAttribute("full-width")&&`${r}--full-width`,this.disabled&&`${r}--disabled`,this.readonly&&`${r}--readonly`,this.isOpen&&`${r}--open`,this.hasAttribute("error")&&`${r}--error`].filter(Boolean).join(" "),this.inputEl?.setAttribute("aria-invalid",String(this.hasAttribute("error"))),this.dropdownEl?.parentElement===document.body&&this.dropdownEl.remove(),this.replaceChildren(),!(this.labelEl&&this.triggerEl&&this.inputEl&&this.dropdownEl))return;const o=this.getAttribute("label")||"";this.labelEl.textContent=o,this.labelEl.hidden=!o,this.inputEl.value=this.inputText,this.inputEl.placeholder=this.is12h?this.showSeconds?"12:00:00 PM":"12:00 PM":this.showSeconds?"12:00:00":"12:00",this.inputEl.disabled=this.disabled,this.inputEl.readOnly=this.readonly;const h=this.triggerEl.querySelector(`.${r}__icon-display`);h&&(h.innerHTML="",h.appendChild(t("clock",n)),h.disabled=this.disabled);const d=this.triggerEl.querySelector(`.${r}__toggle`);if(d&&(d.innerHTML="",d.appendChild(t("chevron-down",n)),d.disabled=this.disabled),this.dropdownEl.className=[`${r}__dropdown`,`${r}__dropdown--${i}`,`${r}__dropdown--${s}`].filter(Boolean).join(" "),this.dropdownEl.setAttribute("aria-label","시간 선택"),this.dropdownEl.hidden=!this.isOpen,this.isOpen&&"dropdown"===this.type&&(this.dropdownEl.replaceChildren(this.renderSelector(!1)),document.body.appendChild(this.dropdownEl),this.dropdownEl.hidden=!1,this.positionDropdown(),requestAnimationFrame(()=>this.positionDropdown())),this.append(this.labelEl,this.triggerEl),this.isOpen&&"modal"===this.type){const t=document.createElement("db-modal");t.setAttribute("open","true"),t.setAttribute("title","시간 선택"),t.setAttribute("size","s"),t.addEventListener("db-close",()=>this.closePicker());const e=this.renderSelector(!0),i=document.createElement("div");i.className=`${r}__modal-footer`;const s=document.createElement("db-button");s.setAttribute("variant","secondary"),s.setAttribute("size","m"),s.textContent="취소",s.addEventListener("click",()=>this.closePicker());const n=document.createElement("db-button");n.setAttribute("variant","primary"),n.setAttribute("size","m"),n.textContent="적용",n.addEventListener("click",()=>{this.handleTimeChange(this.tempHours,this.tempMinutes,this.tempSeconds,this.tempPeriod),this.closePicker()}),i.append(s,n),t.append(e,i),this.appendChild(t)}}}customElements.get("db-time-picker")||customElements.define("db-time-picker",DbTimePicker);export{DbTimePicker};
1
+ import{dbText as t}from"../core/i18n.js";import{createDbIcon as e,getDbComponentIconSize as i}from"./db-icons.js";import"./db-button.js";import{TIME_PICKER_FORMATS as s,TIME_PICKER_SIZES as n,TIME_PICKER_DROPDOWN_POSITIONS as o}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";import{coerceToContract as r}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";const h="designbase-wc-time-picker";class DbTimePicker extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.isOpen=!1,this.hours=12,this.minutes=0,this.seconds=0,this.period="PM",this.inputText="",this.tempHours=12,this.tempMinutes=0,this.tempSeconds=0,this.tempPeriod="PM",this.boundDocumentMouseDown=t=>this.handleDocumentMouseDown(t),this.boundViewportChange=()=>this.handleViewportChange(),this.viewportChangeFrame=null,this.handleDocumentKeyDown=t=>{"Escape"===t.key&&this.isOpen&&(t.preventDefault(),this.closePicker(),this.inputEl?.focus())},this.boundDocumentKeyDown=t=>this.handleDocumentKeyDown(t)}static get observedAttributes(){return["value","default-value","type","format","minute-step","size","disabled","readonly","label","full-width","error","dropdown-position","open"]}connectedCallback(){this.built||this.build(),this.parseTimeString(this.getAttribute("value")??this.getAttribute("default-value")??"12:00"),this.syncInputText(),this.render(),document.addEventListener("mousedown",this.boundDocumentMouseDown),document.addEventListener("keydown",this.boundDocumentKeyDown),window.addEventListener("resize",this.boundViewportChange),window.addEventListener("scroll",this.boundViewportChange,{capture:!0,passive:!0})}disconnectedCallback(){document.removeEventListener("mousedown",this.boundDocumentMouseDown),document.removeEventListener("keydown",this.boundDocumentKeyDown),window.removeEventListener("resize",this.boundViewportChange),window.removeEventListener("scroll",this.boundViewportChange,{capture:!0}),null!==this.viewportChangeFrame&&cancelAnimationFrame(this.viewportChangeFrame),this.viewportChangeFrame=null,this.dropdownEl?.remove()}attributeChangedCallback(t){if(this.built){if("value"===t){const t=this.getAttribute("value");t&&(this.parseTimeString(t),this.syncInputText())}if("open"===t)return this.isOpen=this.hasAttribute("open"),void this.render();this.render()}}get value(){return this.getAttribute("value")||""}set value(t){t?this.setAttribute("value",t):this.removeAttribute("value")}get format(){const t=this.getAttribute("format")||"24h";return s.includes(t)?t:"24h"}set format(t){this.setAttribute("format",t)}get type(){return"modal"===this.getAttribute("type")?"modal":"dropdown"}get is12h(){return"12h"===this.format||"12h-with-seconds"===this.format}get showSeconds(){return"12h-with-seconds"===this.format||"24h-with-seconds"===this.format}get minuteStep(){const t=Number(this.getAttribute("minute-step")||"1");return Number.isFinite(t)&&t>0?t:1}get normalizedSize(){return r(this.getAttribute("size"),n,"m")}get dropdownPosition(){return r(this.getAttribute("dropdown-position"),o,"auto")}get itemHeight(){return"s"===this.normalizedSize?32:"l"===this.normalizedSize?40:28}get disabled(){return this.hasAttribute("disabled")}set disabled(t){this.toggleAttribute("disabled",Boolean(t))}get readonly(){return this.hasAttribute("readonly")}set readonly(t){this.toggleAttribute("readonly",Boolean(t))}build(){this.built=!0,this.labelEl=document.createElement("label"),this.labelEl.className=`${h}__label`,this.triggerEl=document.createElement("div"),this.triggerEl.className=`${h}__trigger`;const e=document.createElement("button");e.type="button",e.className=`${h}__icon-display`,e.setAttribute("aria-label",t("시간 선택 열기")),e.addEventListener("click",()=>this.toggleOpen()),this.inputEl=document.createElement("input"),this.inputEl.type="text",this.inputEl.className=`${h}__input`,this.inputEl.setAttribute("aria-label",t("시간 입력")),this.inputEl.addEventListener("click",()=>this.openPicker()),this.inputEl.addEventListener("blur",()=>this.handleInputBlur()),this.inputEl.addEventListener("input",()=>{this.inputText=this.inputEl?.value||""});const i=document.createElement("button");i.type="button",i.className=`${h}__toggle`,i.setAttribute("aria-label",t("시간 선택 열기")),i.addEventListener("click",()=>this.toggleOpen()),this.triggerEl.append(e,this.inputEl,i),this.dropdownEl=document.createElement("div"),this.dropdownEl.className=`${h}__dropdown`,this.dropdownEl.setAttribute("role","dialog"),this.dropdownEl.setAttribute("aria-modal","false")}pad2(t){return String(t).padStart(2,"0")}to24Hour(t,e){return"AM"===e?12===t?0:t:12===t?12:t+12}formatTimeString(t,e,i,s){const n="AM"===(s||this.period)?"오전":"오후";return this.is12h?`${n} ${this.pad2(t)}:${this.pad2(e)}${this.showSeconds?`:${this.pad2(i)}`:""}`:`${this.pad2(t)}:${this.pad2(e)}${this.showSeconds?`:${this.pad2(i)}`:""}`}toValue24(t,e,i,s){const n=this.is12h?this.to24Hour(t,s||this.period):t;return`${this.pad2(n)}:${this.pad2(e)}${this.showSeconds?`:${this.pad2(i)}`:""}`}parseTimeString(t){const e=t.trim();if(this.is12h){const t=this.showSeconds?/^(오전|오후|AM|PM)?\s*(0?[1-9]|1[0-2]):([0-5]\d):([0-5]\d)\s*(AM|PM|오전|오후)?$/i:/^(오전|오후|AM|PM)?\s*(0?[1-9]|1[0-2]):([0-5]\d)\s*(AM|PM|오전|오후)?$/i,i=e.match(t);if(i){const t=i[1],e=this.showSeconds?i[5]:i[4],s=(t||e||"AM").toUpperCase();return this.hours=parseInt(i[2],10),this.minutes=parseInt(i[3],10),this.seconds=this.showSeconds?parseInt(i[4],10):0,void(this.period="오후"===s||"PM"===s?"PM":"AM")}}else{const t=this.showSeconds?/^([01]?\d|2[0-3]):([0-5]\d):([0-5]\d)$/:/^([01]?\d|2[0-3]):([0-5]\d)$/,i=e.match(t);i&&(this.hours=parseInt(i[1],10),this.minutes=parseInt(i[2],10),this.seconds=this.showSeconds?parseInt(i[3],10):0)}}syncInputText(){this.inputText=this.formatTimeString(this.hours,this.minutes,this.seconds),this.inputEl&&(this.inputEl.value=this.inputText)}emitChange(t){this.setAttribute("value",t),this.dispatchEvent(new CustomEvent("db-change",{detail:{value:t},bubbles:!0,composed:!0}))}handleTimeChange(t,e,i=this.seconds,s){this.hours=t,this.minutes=e,this.seconds=i,s&&(this.period=s);const n=this.formatTimeString(t,e,i,s);this.inputText=n,this.inputEl&&(this.inputEl.value=n),this.emitChange(this.toValue24(t,e,i,s))}handleInputBlur(){if(!this.inputEl)return;const t=this.inputText;this.parseTimeString(this.inputEl.value);this.formatTimeString(this.hours,this.minutes,this.seconds)!==t||this.inputEl.value.trim()?this.handleTimeChange(this.hours,this.minutes,this.seconds,this.period):this.syncInputText()}openPicker(){this.disabled||this.readonly||(this.isOpen=!0,"modal"===this.type&&(this.tempHours=this.hours,this.tempMinutes=this.minutes,this.tempSeconds=this.seconds,this.tempPeriod=this.period),this.render())}closePicker(){this.isOpen=!1,this.render()}toggleOpen(){this.isOpen?this.closePicker():this.openPicker()}handleDocumentMouseDown(t){if(!this.isOpen)return;const e=t.target;this.contains(e)||this.dropdownEl?.contains(e)||this.closePicker()}handleViewportChange(){this.isOpen&&"dropdown"===this.type&&null===this.viewportChangeFrame&&(this.viewportChangeFrame=requestAnimationFrame(()=>{this.viewportChangeFrame=null,this.isOpen&&"dropdown"===this.type&&this.positionDropdown()}))}positionDropdown(){if(!this.isOpen||"dropdown"!==this.type||!this.triggerEl||!this.dropdownEl||this.dropdownEl.hidden)return;const t=this.dropdownEl,e=this.triggerEl.getBoundingClientRect();t.style.width="";const i=t.getBoundingClientRect(),s=Math.max(0,window.innerWidth-16),n=Math.min(i.width,s);t.style.width=`${Math.round(n)}px`;const o=this.dropdownPosition,r=e.bottom+4+i.height<=window.innerHeight-8,h=e.top-4-i.height>=8,d="auto"===o?r||!h?"bottom":"top":o.startsWith("top")?"top":"bottom",a="auto"===o?e.left+n>window.innerWidth-8?"end":"start":o.endsWith("end")?"end":"start",l="end"===a?e.right-n:e.left,u=Math.max(8,Math.min(l,window.innerWidth-n-8)),p="top"===d?e.top-i.height-4:e.bottom+4,c=Math.max(8,Math.min(p,window.innerHeight-i.height-8));t.style.left=`${Math.round(u)}px`,t.style.top=`${Math.round(c)}px`,t.dataset.placement=`${d}-${a}`}createWheel(t){const{label:e,items:i,value:s,onChange:n,formatter:o}=t,r=document.createElement("div");r.className=`${h}__wheel-container`;const d=document.createElement("div");d.className=`${h}__wheel-label`,d.textContent=e,r.appendChild(d);const a=document.createElement("div");a.className=`${h}__wheel-viewport`;const l=document.createElement("div");l.className=`${h}__wheel`,l.setAttribute("role","listbox"),l.setAttribute("aria-label",e);let u=null,p=!1,c=!1,m=0,b=0;const indexOfValue=t=>i.findIndex(e=>e===t),scrollToIndex=(t,e=!0)=>{const i=t*this.itemHeight;p=!0,l.scrollTo({top:i,behavior:e?"smooth":"auto"}),window.setTimeout(()=>{p=!1},150)},fmt=t=>o?o(t):"number"==typeof t?this.pad2(t):String(t);i.forEach(t=>{const e=document.createElement("div");e.className=[`${h}__wheel-item`,t===s&&`${h}__wheel-item--selected`].filter(Boolean).join(" "),e.setAttribute("role","option"),e.setAttribute("aria-selected",String(t===s)),e.textContent=fmt(t),e.addEventListener("click",()=>{const e=indexOfValue(t);scrollToIndex(e,!0),t!==s&&n(t)}),l.appendChild(e)});const handleScroll=()=>{p||(u&&window.clearTimeout(u),u=window.setTimeout(()=>{const t=l.scrollTop,e=Math.round(t/this.itemHeight),o=Math.max(0,Math.min(i.length-1,e)),r=i[o];scrollToIndex(o,!0),r!==s&&n(r)},120))};l.addEventListener("scroll",handleScroll,{passive:!0}),l.addEventListener("pointerdown",t=>{c=!0,m=t.clientY,b=l.scrollTop,l.setPointerCapture(t.pointerId),l.style.cursor="grabbing"}),l.addEventListener("pointermove",t=>{c&&(l.scrollTop=b-(t.clientY-m))});const endDrag=t=>{c&&(c=!1,l.releasePointerCapture(t.pointerId),l.style.cursor="grab",handleScroll())};l.addEventListener("pointerup",endDrag),l.addEventListener("pointercancel",endDrag);const w=indexOfValue(s);w>=0&&requestAnimationFrame(()=>scrollToIndex(w,!1));const g=document.createElement("div");g.className=`${h}__wheel-center-indicator`,g.setAttribute("aria-hidden","true");const E=document.createElement("div");E.className=`${h}__wheel-fade wheel-fade--top`,E.setAttribute("aria-hidden","true");const v=document.createElement("div");return v.className=`${h}__wheel-fade wheel-fade--bottom`,v.setAttribute("aria-hidden","true"),a.append(l,g,E,v),r.appendChild(a),r}renderSelector(i=!1){const s=document.createElement("div");s.className=`${h}__selector`;const n=document.createElement("div");n.className=`${h}__picker-wheels`;const o=this.is12h?Array.from({length:12},(t,e)=>e+1):Array.from({length:24},(t,e)=>e),r=Array.from({length:Math.floor(60/this.minuteStep)},(t,e)=>e*this.minuteStep),d=Array.from({length:60},(t,e)=>e),a=i?this.tempHours:this.hours,l=i?this.tempMinutes:this.minutes,u=i?this.tempSeconds:this.seconds,p=i?this.tempPeriod:this.period,onChange=(t,e,s,n)=>{if(i)return this.tempHours=t,this.tempMinutes=e,this.tempSeconds=s,void(this.tempPeriod=n);this.handleTimeChange(t,e,s,n)};if(n.appendChild(this.createWheel({label:t("시간"),items:o,value:a,onChange:t=>onChange(t,l,u,p)})),n.appendChild(this.createWheel({label:t("분"),items:r,value:r.includes(l)?l:r[0],onChange:t=>onChange(a,t,u,p)})),this.showSeconds&&n.appendChild(this.createWheel({label:t("초"),items:d,value:u,onChange:t=>onChange(a,l,t,p)})),this.is12h&&n.appendChild(this.createWheel({label:t("오전/오후"),items:["AM","PM"],value:p,onChange:t=>onChange(a,l,u,t),formatter:t=>"AM"===t?"오전":"오후"})),s.appendChild(n),i)return s;const c=document.createElement("div");c.className=`${h}__dropdown-actions`;const m=document.createElement("div");m.className=`${h}__preview`,m.setAttribute("aria-live","polite"),m.appendChild(e("clock",16));const b=document.createElement("div");b.className=`${h}__preview-time`,b.textContent=this.formatTimeString(a,l,u,p),m.appendChild(b);const w=document.createElement("db-button");return w.setAttribute("variant","primary"),w.setAttribute("size","m"),w.textContent=t("선택"),w.addEventListener("click",()=>{this.handleTimeChange(a,l,u,p),this.closePicker()}),c.append(m,w),s.appendChild(c),s}render(){const s=this.normalizedSize,n=this.dropdownPosition,o=i(s);if(this.className=[h,`${h}--${s}`,this.hasAttribute("full-width")&&`${h}--full-width`,this.disabled&&`${h}--disabled`,this.readonly&&`${h}--readonly`,this.isOpen&&`${h}--open`,this.hasAttribute("error")&&`${h}--error`].filter(Boolean).join(" "),this.inputEl?.setAttribute("aria-invalid",String(this.hasAttribute("error"))),this.dropdownEl?.parentElement===document.body&&this.dropdownEl.remove(),this.replaceChildren(),!(this.labelEl&&this.triggerEl&&this.inputEl&&this.dropdownEl))return;const r=this.getAttribute("label")||"";this.labelEl.textContent=r,this.labelEl.hidden=!r,this.inputEl.value=this.inputText,this.inputEl.placeholder=this.is12h?this.showSeconds?"12:00:00 PM":"12:00 PM":this.showSeconds?"12:00:00":"12:00",this.inputEl.disabled=this.disabled,this.inputEl.readOnly=this.readonly;const d=this.triggerEl.querySelector(`.${h}__icon-display`);d&&(d.innerHTML="",d.appendChild(e("clock",o)),d.disabled=this.disabled);const a=this.triggerEl.querySelector(`.${h}__toggle`);if(a&&(a.innerHTML="",a.appendChild(e("chevron-down",o)),a.disabled=this.disabled),this.dropdownEl.className=[`${h}__dropdown`,`${h}__dropdown--${s}`,`${h}__dropdown--${n}`].filter(Boolean).join(" "),this.dropdownEl.setAttribute("aria-label",t("시간 선택")),this.dropdownEl.hidden=!this.isOpen,this.isOpen&&"dropdown"===this.type&&(this.dropdownEl.replaceChildren(this.renderSelector(!1)),document.body.appendChild(this.dropdownEl),this.dropdownEl.hidden=!1,this.positionDropdown(),requestAnimationFrame(()=>this.positionDropdown())),this.append(this.labelEl,this.triggerEl),this.isOpen&&"modal"===this.type){const e=document.createElement("db-modal");e.setAttribute("open","true"),e.setAttribute("title",t("시간 선택")),e.setAttribute("size","s"),e.addEventListener("db-close",()=>this.closePicker());const i=this.renderSelector(!0),s=document.createElement("div");s.className=`${h}__modal-footer`;const n=document.createElement("db-button");n.setAttribute("variant","secondary"),n.setAttribute("size","m"),n.textContent=t("취소"),n.addEventListener("click",()=>this.closePicker());const o=document.createElement("db-button");o.setAttribute("variant","primary"),o.setAttribute("size","m"),o.textContent=t("적용"),o.addEventListener("click",()=>{this.handleTimeChange(this.tempHours,this.tempMinutes,this.tempSeconds,this.tempPeriod),this.closePicker()}),s.append(n,o),e.append(i,s),this.appendChild(e)}}}customElements.get("db-time-picker")||customElements.define("db-time-picker",DbTimePicker);export{DbTimePicker};
@@ -0,0 +1,57 @@
1
+ import './db-button.js';
2
+ import './db-tooltip.js';
3
+ import './db-popover.js';
4
+
5
+ interface DesignToolOption {
6
+ id: string;
7
+ label: string;
8
+ icon?: string;
9
+ shortcut?: string;
10
+ disabled?: boolean;
11
+ }
12
+ interface DesignToolAction {
13
+ id: string;
14
+ optionId?: string;
15
+ option?: DesignToolOption;
16
+ }
17
+ /** Icon action with a portalled tooltip and optional click/long-press menu. */
18
+ declare class DbToolButton extends HTMLElement {
19
+ static get observedAttributes(): string[];
20
+ private static openMenus;
21
+ private button?;
22
+ private tooltip?;
23
+ private menuPopover?;
24
+ private menu?;
25
+ private timer?;
26
+ private held;
27
+ private pointer?;
28
+ private menuSignature;
29
+ private focusFrame?;
30
+ get options(): DesignToolOption[];
31
+ set options(value: DesignToolOption[] | string);
32
+ get value(): string;
33
+ set value(v: string);
34
+ get disabled(): boolean;
35
+ connectedCallback(): void;
36
+ disconnectedCallback(): void;
37
+ attributeChangedCallback(): void;
38
+ private ensure;
39
+ private sync;
40
+ private activate;
41
+ openMenu(): void;
42
+ private outsidePress;
43
+ private releaseMenuOwner;
44
+ closeMenu(restoreFocus?: boolean): void;
45
+ private menuKey;
46
+ private pressStart;
47
+ private pressEnd;
48
+ private clearPress;
49
+ }
50
+ declare global {
51
+ interface HTMLElementTagNameMap {
52
+ 'db-tool-button': DbToolButton;
53
+ }
54
+ }
55
+
56
+ export { DbToolButton };
57
+ export type { DesignToolAction, DesignToolOption };
@@ -0,0 +1 @@
1
+ import"./db-button.js";import"./db-tooltip.js";import"./db-popover.js";import{isDbIconName as t,createDbIcon as e}from"./db-icons.js";import{dbUid as i}from"../core/uid.js";class DbToolButton extends HTMLElement{constructor(){super(...arguments),this.held=!1,this.menuSignature="",this.outsidePress=t=>{const e=t.composedPath();e.includes(this)||this.menu&&e.includes(this.menu)||this.closeMenu(!1)},this.menuKey=t=>{const e=Array.from(this.menu?.querySelectorAll("button:not(:disabled)")||[]),i=e.indexOf(document.activeElement);if(["ArrowDown","ArrowUp","Home","End","Escape","Tab"].includes(t.key)){if(t.stopPropagation(),"Escape"===t.key)return t.preventDefault(),void this.closeMenu();if("Tab"===t.key)return void this.closeMenu();t.preventDefault();const s="Home"===t.key?0:"End"===t.key?e.length-1:(i+("ArrowDown"===t.key?1:-1)+e.length)%e.length;e[s]?.focus()}},this.pressStart=t=>{0===t.button&&!this.disabled&&this.options.length&&(this.clearPress(),this.held=!1,this.pointer={x:t.clientX,y:t.clientY,id:t.pointerId},this.timer=setTimeout(()=>{this.held=!0,this.openMenu()},450))},this.pressEnd=()=>{this.clearPress()}}static get observedAttributes(){return["tool-id","label","icon","text","shortcut","active","toggle","disabled","options","value","menu-trigger","size","tone","placement"]}get options(){try{const t=JSON.parse(this.getAttribute("options")||"[]");return Array.isArray(t)?t.filter(t=>t&&"string"==typeof t.id&&"string"==typeof t.label):[]}catch{return[]}}set options(t){this.setAttribute("options","string"==typeof t?t:JSON.stringify(t))}get value(){return this.getAttribute("value")||""}set value(t){this.setAttribute("value",t)}get disabled(){return this.hasAttribute("disabled")}connectedCallback(){this.ensure(),this.sync()}disconnectedCallback(){this.clearPress(),this.closeMenu(!1)}attributeChangedCallback(){this.isConnected&&(this.ensure(),this.sync())}ensure(){this.button||(this.classList.add("designbase-wc-tool-button"),this.tooltip=document.createElement("db-tooltip"),this.tooltip.setAttribute("position","bottom"),this.tooltip.setAttribute("focus-target","child"),this.tooltip.setAttribute("delay","350"),this.tooltip.setAttribute("size","s"),this.button=document.createElement("db-button"),this.button.id=i("design-tool"),this.button.setAttribute("variant","ghost"),this.button.setAttribute("type","button"),this.tooltip.append(this.button),this.menuPopover=document.createElement("db-popover"),this.menuPopover.setAttribute("trigger","manual"),this.menuPopover.setAttribute("open","false"),this.menuPopover.setAttribute("trigger-id",this.button.id),this.menuPopover.setAttribute("show-arrow","false"),this.menuPopover.setAttribute("size","s"),this.menuPopover.setAttribute("close-on-escape","false"),this.menuPopover.setAttribute("max-width","280"),this.menu=document.createElement("div"),this.menu.className="designbase-wc-tool-button__menu",this.menu.setAttribute("slot","content"),this.menu.setAttribute("role","menu"),this.menu.addEventListener("keydown",this.menuKey),this.menuPopover.append(this.menu),this.menuPopover.addEventListener("db-open-change",t=>{if(t.target!==this.menuPopover)return;const e=t.detail.open;this.button.setAttribute("aria-expanded",String(e)),this.tooltip.toggleAttribute("disabled",e),e||this.releaseMenuOwner()}),this.button.addEventListener("db-click",t=>{t.stopPropagation(),this.disabled||(this.held?this.held=!1:this.options.length&&"long-press"!==this.getAttribute("menu-trigger")?this.menuPopover?.open?this.closeMenu():this.openMenu():this.activate(this.options.find(t=>t.id===this.value)||this.options[0]))}),this.button.addEventListener("pointerdown",this.pressStart),this.button.addEventListener("pointerup",this.pressEnd),this.button.addEventListener("pointerleave",()=>this.clearPress()),this.button.addEventListener("pointercancel",()=>{this.held=!0,this.clearPress()}),this.button.addEventListener("pointermove",t=>{this.pointer&&Math.hypot(t.clientX-this.pointer.x,t.clientY-this.pointer.y)>8&&this.clearPress()}),this.button.addEventListener("contextmenu",t=>{this.options.length&&(t.preventDefault(),this.openMenu())}),this.button.addEventListener("keydown",t=>{const e="vertical"===this.closest("db-design-toolbar,db-context-bar")?.getAttribute("orientation");(t.key===(e?"ArrowRight":"ArrowDown")||t.altKey&&"Enter"===t.key)&&this.options.length&&(t.preventDefault(),t.stopPropagation(),this.openMenu())}),this.append(this.tooltip,this.menuPopover))}sync(){if(!this.button||!this.menu)return;const i=this.options,s=i.find(t=>t.id===this.value),o=(this.hasAttribute("text")?this.getAttribute("label"):s?.label)||this.getAttribute("label")||"도구",n=s?.icon||this.getAttribute("icon")||"",r=s?.shortcut||this.getAttribute("shortcut"),u=`${o}${r?` · ${r}`:""}${i.length&&"long-press"===this.getAttribute("menu-trigger")?" · 길게 눌러 도구 더 보기":""}`;this.button.setAttribute("aria-label",o),this.button.setAttribute("size","s"===this.getAttribute("size")?"s":"m"),this.button.toggleAttribute("disabled",this.disabled),this.button.toggleAttribute("icon-only",!this.hasAttribute("text")),this.hasAttribute("toggle")||this.hasAttribute("active")?this.button.setAttribute("aria-pressed",String(this.hasAttribute("active"))):this.button.removeAttribute("aria-pressed"),this.button.dataset.toolId=this.getAttribute("tool-id")||"",i.length?(this.button.setAttribute("aria-haspopup","menu"),this.button.setAttribute("aria-expanded",String(!!this.menuPopover?.open))):(this.button.removeAttribute("aria-haspopup"),this.button.removeAttribute("aria-expanded")),this.tooltip.content=u,this.menuPopover.setAttribute("placement",(this.getAttribute("placement")||"bottom-start").replace(/^(top|bottom)-left$/,"$1-start").replace(/^(top|bottom)-right$/,"$1-end")),this.menu.setAttribute("aria-label",this.getAttribute("label")||o),this.menu.dataset.tone=this.getAttribute("tone")||"default";const h=`${n}|${this.getAttribute("text")||""}|${i.length>0}`;if(this.button.dataset.signature!==h){this.button.dataset.signature=h;const s=[];if(n&&t(n)&&s.push(e(n,20)),this.hasAttribute("text")){const t=document.createElement("span");t.textContent=this.getAttribute("text"),s.push(t)}if(i.length){const t=document.createElement("span");t.className="designbase-wc-tool-button__indicator",t.setAttribute("aria-hidden","true"),s.push(t)}this.button.replaceChildren(...s)}this.disabled&&this.menuPopover?.open&&this.closeMenu(!1);const a="long-press"===this.getAttribute("menu-trigger")||!!this.value,l=JSON.stringify([i,this.value,a]);this.menuSignature!==l&&(this.menuSignature=l,this.menu.replaceChildren(),i.forEach(i=>{const s=document.createElement("button");s.type="button",s.disabled=!!i.disabled,s.setAttribute("role",a?"menuitemradio":"menuitem"),a&&s.setAttribute("aria-checked",String(i.id===this.value)),s.tabIndex=-1,i.icon&&t(i.icon)&&s.append(e(i.icon,20));const o=document.createElement("span");if(o.textContent=i.label,s.append(o),i.shortcut){const t=document.createElement("kbd");t.textContent=i.shortcut,s.append(t)}i.id===this.value&&s.append(e("done",14)),s.addEventListener("click",()=>{a&&(this.value=i.id),this.closeMenu(!0),this.activate(i)}),this.menu.append(s)}))}activate(t){this.disabled||t?.disabled||this.dispatchEvent(new CustomEvent("db-tool-action",{detail:{id:this.getAttribute("tool-id")||"",optionId:t?.id,option:t},bubbles:!0,composed:!0}))}openMenu(){if(this.disabled||!this.options.length||!this.menuPopover)return;this.clearPress();const t=DbToolButton.openMenus.get(this.ownerDocument);t&&t!==this&&t.closeMenu(!1),DbToolButton.openMenus.set(this.ownerDocument,this),this.ownerDocument.addEventListener("pointerdown",this.outsidePress,!0),this.menuPopover.open=!0,this.button?.setAttribute("aria-expanded","true"),this.tooltip?.setAttribute("disabled",""),this.focusFrame&&cancelAnimationFrame(this.focusFrame),this.focusFrame=requestAnimationFrame(()=>{this.menu?.closest('[role="dialog"]')?.setAttribute("aria-label",this.getAttribute("label")||"도구 메뉴");const t=Array.from(this.menu?.querySelectorAll("button:not(:disabled)")||[]);(t.find(t=>"true"===t.getAttribute("aria-checked"))||t[0])?.focus()})}releaseMenuOwner(){DbToolButton.openMenus.get(this.ownerDocument)===this&&DbToolButton.openMenus.delete(this.ownerDocument),this.ownerDocument.removeEventListener("pointerdown",this.outsidePress,!0)}closeMenu(t=!0){this.releaseMenuOwner(),this.focusFrame&&cancelAnimationFrame(this.focusFrame),this.menuPopover?.setAttribute("open","false"),this.button?.setAttribute("aria-expanded","false"),this.tooltip?.removeAttribute("disabled"),t&&this.button?.focus()}clearPress(){this.timer&&clearTimeout(this.timer),this.timer=void 0,this.pointer=void 0}}DbToolButton.openMenus=new WeakMap,customElements.get("db-tool-button")||customElements.define("db-tool-button",DbToolButton);export{DbToolButton};
@@ -1 +1 @@
1
- import{createDbIcon as e}from"./db-icons.js";import{TOP_BANNER_VARIANTS as t}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";class DbTopBanner extends HTMLElement{static get observedAttributes(){return["badge-text","content","image","link-text","variant","fixed","dismissed"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}isDarkBg(e){return"primary"===e||"dark"===e}createLinkButton(t,n){const s=this.isDarkBg(t),a=document.createElement("db-button");a.setAttribute("variant",s?"secondary":"ghost"),a.setAttribute("size","s"),a.classList.add("designbase-wc-top-banner__link");const i=document.createElement("span");i.textContent=n;const r=document.createElement("span");return r.className="designbase-wc-button__end-icon",r.append(e("chevron-right",16)),a.append(i,r),a.addEventListener("db-click",()=>{this.dispatchEvent(new CustomEvent("db-link",{bubbles:!0,composed:!0}))}),a}createCloseButton(){const t=document.createElement("db-button");return t.setAttribute("variant","ghost"),t.setAttribute("size","m"),t.setAttribute("icon-only",""),t.setAttribute("aria-label","닫기"),t.classList.add("designbase-wc-top-banner__close"),t.append(e("close",20)),t.addEventListener("db-click",()=>{this.setAttribute("dismissed",""),this.dispatchEvent(new CustomEvent("db-close",{bubbles:!0,composed:!0})),this.render()}),t}createBadge(e,t){const n=document.createElement("db-badge");return n.setAttribute("size","s"),n.setAttribute("variant",t?"secondary":"primary"),n.setAttribute("type","text"),n.textContent=e,n.classList.add("designbase-wc-top-banner__badge"),n}render(){if(this.hasAttribute("dismissed"))return this.replaceChildren(),this.className="designbase-wc-top-banner designbase-wc-top-banner--dismissed",void(this.style.display="none");this.style.display="";const e=this.getAttribute("badge-text")||"",n=this.getAttribute("content")||"",s=this.getAttribute("image")||"",a=this.getAttribute("link-text")||"",i=this.getAttribute("variant")||"primary",r=t.includes(i)?i:"primary",d=this.hasAttribute("fixed"),o=this.isDarkBg(r);if(this.className=["designbase-wc-top-banner",`designbase-wc-top-banner--${r}`,d&&"designbase-wc-top-banner--fixed",s&&"designbase-wc-top-banner--has-image"].filter(Boolean).join(" "),this.setAttribute("role","alert"),s?this.style.setProperty("--db-top-banner-bg-image",`url("${s}")`):this.style.removeProperty("--db-top-banner-bg-image"),this.replaceChildren(),s){const e=document.createElement("div");e.className="designbase-wc-top-banner__background",e.setAttribute("aria-hidden","true");const t=document.createElement("div");t.className="designbase-wc-top-banner__overlay",t.setAttribute("aria-hidden","true"),this.append(e,t)}const c=document.createElement("div");c.className="designbase-wc-top-banner__container";const b=document.createElement("div");b.className="designbase-wc-top-banner__content",e&&b.append(this.createBadge(e,o));const l=document.createElement("span");l.className="designbase-wc-top-banner__text",l.textContent=n,b.append(l),a&&b.append(this.createLinkButton(r,a)),c.append(b),c.append(this.createCloseButton()),this.append(c)}}customElements.get("db-top-banner")||customElements.define("db-top-banner",DbTopBanner);export{DbTopBanner};
1
+ import{dbText as e}from"../core/i18n.js";import{createDbIcon as t}from"./db-icons.js";import{TOP_BANNER_VARIANTS as n}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";class DbTopBanner extends HTMLElement{static get observedAttributes(){return["badge-text","content","image","link-text","variant","fixed","dismissed"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}isDarkBg(e){return"primary"===e||"dark"===e}createLinkButton(e,n){const s=this.isDarkBg(e),a=document.createElement("db-button");a.setAttribute("variant",s?"secondary":"ghost"),a.setAttribute("size","s"),a.classList.add("designbase-wc-top-banner__link");const i=document.createElement("span");i.textContent=n;const r=document.createElement("span");return r.className="designbase-wc-button__end-icon",r.append(t("chevron-right",16)),a.append(i,r),a.addEventListener("db-click",()=>{this.dispatchEvent(new CustomEvent("db-link",{bubbles:!0,composed:!0}))}),a}createCloseButton(){const n=document.createElement("db-button");return n.setAttribute("variant","ghost"),n.setAttribute("size","m"),n.setAttribute("icon-only",""),n.setAttribute("aria-label",e("닫기")),n.classList.add("designbase-wc-top-banner__close"),n.append(t("close",20)),n.addEventListener("db-click",()=>{this.setAttribute("dismissed",""),this.dispatchEvent(new CustomEvent("db-close",{bubbles:!0,composed:!0})),this.render()}),n}createBadge(e,t){const n=document.createElement("db-badge");return n.setAttribute("size","s"),n.setAttribute("variant",t?"secondary":"primary"),n.setAttribute("type","text"),n.textContent=e,n.classList.add("designbase-wc-top-banner__badge"),n}render(){if(this.hasAttribute("dismissed"))return this.replaceChildren(),this.className="designbase-wc-top-banner designbase-wc-top-banner--dismissed",void(this.style.display="none");this.style.display="";const e=this.getAttribute("badge-text")||"",t=this.getAttribute("content")||"",s=this.getAttribute("image")||"",a=this.getAttribute("link-text")||"",i=this.getAttribute("variant")||"primary",r=n.includes(i)?i:"primary",d=this.hasAttribute("fixed"),o=this.isDarkBg(r);if(this.className=["designbase-wc-top-banner",`designbase-wc-top-banner--${r}`,d&&"designbase-wc-top-banner--fixed",s&&"designbase-wc-top-banner--has-image"].filter(Boolean).join(" "),this.setAttribute("role","alert"),s?this.style.setProperty("--db-top-banner-bg-image",`url("${s}")`):this.style.removeProperty("--db-top-banner-bg-image"),this.replaceChildren(),s){const e=document.createElement("div");e.className="designbase-wc-top-banner__background",e.setAttribute("aria-hidden","true");const t=document.createElement("div");t.className="designbase-wc-top-banner__overlay",t.setAttribute("aria-hidden","true"),this.append(e,t)}const c=document.createElement("div");c.className="designbase-wc-top-banner__container";const b=document.createElement("div");b.className="designbase-wc-top-banner__content",e&&b.append(this.createBadge(e,o));const p=document.createElement("span");p.className="designbase-wc-top-banner__text",p.textContent=t,b.append(p),a&&b.append(this.createLinkButton(r,a)),c.append(b),c.append(this.createCloseButton()),this.append(c)}}customElements.get("db-top-banner")||customElements.define("db-top-banner",DbTopBanner);export{DbTopBanner};
@@ -1 +1 @@
1
- import{createDbIcon as t}from"./db-icons.js";import{createFocusTrap as e}from"../core/focus-trap.js";const i="designbase-wc-tutorial";class DbTutorial extends HTMLElement{constructor(){super(...arguments),this.internalStep=0,this.targetElement=null,this.keyHandler=null,this.resizeHandler=null,this.isMounted=!1}static get observedAttributes(){return["open","steps","current-step","indicator-type","indicator-size"]}connectedCallback(){this.style.display="contents",this.syncStepFromAttribute(),this.syncOpenState()}disconnectedCallback(){this.detachListeners(),this.clearTargetHighlight(),this.unmount()}attributeChangedCallback(t){this.isConnected&&("open"!==t?("current-step"===t&&this.syncStepFromAttribute(),this.isMounted&&this.render()):this.syncOpenState())}get steps(){return this.parseSteps()}set steps(t){this.setAttribute("steps","string"==typeof t?t:JSON.stringify(t))}parseSteps(){try{const t=JSON.parse(this.getAttribute("steps")||"[]");return Array.isArray(t)?t:[]}catch{return[]}}syncStepFromAttribute(){if(!this.hasAttribute("current-step"))return;const t=Number(this.getAttribute("current-step")||0);Number.isFinite(t)&&(this.internalStep=Math.max(0,t))}isOpenRequested(){return this.hasAttribute("open")&&"false"!==this.getAttribute("open")}getCurrentStepData(){const t=this.parseSteps();if(!t.length)return;const e=Math.min(Math.max(this.internalStep,0),t.length-1);return{step:t[e],index:e,steps:t}}syncOpenState(){if(this.isOpenRequested())return this.mount(),void this.attachListeners();this.detachListeners(),this.clearTargetHighlight(),this.unmount()}mount(){this.root||(this.root=document.createElement("div"),this.root.className=i,this.root.setAttribute("role","dialog"),this.root.setAttribute("aria-modal","true"),this.root.tabIndex=-1,this.ensureStructure()),this.root.isConnected||document.body.append(this.root),this.isMounted=!0,this.render(),this.popoverEl&&!this.focusTrap&&(this.focusTrap=e(this.popoverEl),requestAnimationFrame(()=>this.focusTrap?.activate()))}unmount(){this.isMounted=!1,this.focusTrap?.deactivate(),this.focusTrap=void 0,this.root?.remove()}ensureStructure(){if(!this.root||this.popoverEl)return;this.overlayEl=document.createElement("div"),this.overlayEl.className=`${i}__overlay`,this.overlayEl.addEventListener("click",()=>this.handleEnd()),this.popoverEl=document.createElement("div"),this.popoverEl.className=`${i}__popover`,this.closeButtonEl=document.createElement("button"),this.closeButtonEl.type="button",this.closeButtonEl.className=`${i}__close-button`,this.closeButtonEl.setAttribute("aria-label","튜토리얼 종료"),this.closeButtonEl.append(t("close",16)),this.closeButtonEl.addEventListener("click",()=>this.handleEnd());const e=document.createElement("div");e.className=`${i}__content`;const s=document.createElement("div");s.className=`${i}__text`,this.titleEl=document.createElement("h3"),this.titleEl.id="tutorial-title",this.titleEl.className=`${i}__title`,this.descriptionEl=document.createElement("p"),this.descriptionEl.id="tutorial-content",this.descriptionEl.className=`${i}__description`,s.append(this.titleEl,this.descriptionEl);const n=document.createElement("div");n.className=`${i}__navigation`,this.prevButtonEl=document.createElement("db-button"),this.prevButtonEl.setAttribute("variant","secondary"),this.prevButtonEl.setAttribute("size","s"),this.prevButtonEl.setAttribute("start-icon","chevron-left"),this.prevButtonEl.textContent="이전",this.prevButtonEl.addEventListener("db-click",()=>this.handlePrev()),this.indicatorEl=document.createElement("db-indicator"),this.indicatorEl.addEventListener("db-step-click",t=>{const e=t.detail;"number"==typeof e.index&&this.goToStep(e.index)}),this.nextButtonEl=document.createElement("db-button"),this.nextButtonEl.setAttribute("variant","primary"),this.nextButtonEl.setAttribute("size","s"),this.nextButtonEl.addEventListener("db-click",()=>this.handleNext()),n.append(this.prevButtonEl,this.indicatorEl,this.nextButtonEl),e.append(s,n),this.popoverEl.append(this.closeButtonEl,e),this.root.append(this.overlayEl,this.popoverEl)}attachListeners(){this.keyHandler||(this.keyHandler=t=>{"Escape"===t.key?this.handleEnd():"ArrowLeft"===t.key?this.handlePrev():"ArrowRight"===t.key&&this.handleNext()},document.addEventListener("keydown",this.keyHandler)),this.resizeHandler||(this.resizeHandler=()=>this.updateTargetElement(),window.addEventListener("resize",this.resizeHandler))}detachListeners(){this.keyHandler&&(document.removeEventListener("keydown",this.keyHandler),this.keyHandler=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null)}clearTargetHighlight(){this.targetElement&&(this.targetElement.classList.remove(`${i}__target`),this.targetElement.style.position="",this.targetElement.style.zIndex="",this.targetElement=null)}updateTargetElement(){const t=this.getCurrentStepData();if(!t?.step.target||!this.popoverEl)return;this.clearTargetHighlight();const e=document.querySelector(t.step.target);if(!e)return;this.targetElement=e,e.style.position="relative",e.style.zIndex="1000",e.classList.add(`${i}__target`);const s=e.getBoundingClientRect(),n=window.innerWidth,r=window.innerHeight,o=300,a=200,h=16;let l="auto"!==t.step.placement&&t.step.placement?t.step.placement:"bottom",d=s.bottom+h,c=s.left+s.width/2-150;if("auto"===t.step.placement)s.top-a-h>0?(l="top",d=s.top-a-h):s.bottom+a+h<r?(l="bottom",d=s.bottom+h):s.left-o-h>0?(l="left",d=s.top+s.height/2-100,c=s.left-o-h):(l="right",d=s.top+s.height/2-100,c=s.right+h);else switch(l){case"top":d=s.top-a-h;break;case"bottom":d=s.bottom+h;break;case"left":d=s.top+s.height/2-100,c=s.left-o-h;break;case"right":d=s.top+s.height/2-100,c=s.right+h}c=Math.max(h,Math.min(c,n-o-h)),d=Math.max(h,Math.min(d,r-a-h)),this.popoverEl.className=[`${i}__popover`,`${i}__popover--${l}`].join(" "),this.popoverEl.style.top=`${d}px`,this.popoverEl.style.left=`${c}px`}emitStepChange(t){this.dispatchEvent(new CustomEvent("db-step-change",{bubbles:!0,composed:!0,detail:{step:t}}))}goToStep(t){const e=this.parseSteps();e.length&&(this.clearTargetHighlight(),this.internalStep=Math.min(Math.max(t,0),e.length-1),this.setAttribute("current-step",String(this.internalStep)),this.emitStepChange(this.internalStep),this.render())}handlePrev(){const t=this.getCurrentStepData();!t||t.index<=0||(this.dispatchEvent(new CustomEvent("db-prev",{bubbles:!0,composed:!0})),this.goToStep(t.index-1))}handleNext(){const t=this.getCurrentStepData();t&&(t.index>=t.steps.length-1?this.handleEnd():(this.dispatchEvent(new CustomEvent("db-next",{bubbles:!0,composed:!0})),this.goToStep(t.index+1)))}handleEnd(){this.dispatchEvent(new CustomEvent("db-end",{bubbles:!0,composed:!0})),this.setAttribute("open","false")}render(){const t=this.getCurrentStepData();if(!t||!this.root)return;const{step:e,index:i,steps:s}=t,n=0===i,r=i===s.length-1;this.titleEl&&(this.titleEl.textContent=e.title||"",this.titleEl.hidden=!e.title),this.descriptionEl&&(this.descriptionEl.textContent=e.content||"",this.descriptionEl.hidden=!e.content),this.prevButtonEl&&(this.prevButtonEl.hidden=n),this.nextButtonEl&&(this.nextButtonEl.textContent=r?"완료":"다음",r?this.nextButtonEl.removeAttribute("end-icon"):this.nextButtonEl.setAttribute("end-icon","chevron-right")),this.indicatorEl&&(this.indicatorEl.setAttribute("current",String(i)),this.indicatorEl.setAttribute("total",String(s.length)),this.indicatorEl.setAttribute("type",this.getAttribute("indicator-type")||"numbers"),this.indicatorEl.setAttribute("size",this.getAttribute("indicator-size")||"m")),this.updateTargetElement()}}customElements.get("db-tutorial")||customElements.define("db-tutorial",DbTutorial);export{DbTutorial};
1
+ import{dbText as t}from"../core/i18n.js";import{createDbIcon as e}from"./db-icons.js";import{createFocusTrap as i}from"../core/focus-trap.js";const s="designbase-wc-tutorial";class DbTutorial extends HTMLElement{constructor(){super(...arguments),this.internalStep=0,this.targetElement=null,this.keyHandler=null,this.resizeHandler=null,this.isMounted=!1}static get observedAttributes(){return["open","steps","current-step","indicator-type","indicator-size"]}connectedCallback(){this.style.display="contents",this.syncStepFromAttribute(),this.syncOpenState()}disconnectedCallback(){this.detachListeners(),this.clearTargetHighlight(),this.unmount()}attributeChangedCallback(t){this.isConnected&&("open"!==t?("current-step"===t&&this.syncStepFromAttribute(),this.isMounted&&this.render()):this.syncOpenState())}get steps(){return this.parseSteps()}set steps(t){this.setAttribute("steps","string"==typeof t?t:JSON.stringify(t))}parseSteps(){try{const t=JSON.parse(this.getAttribute("steps")||"[]");return Array.isArray(t)?t:[]}catch{return[]}}syncStepFromAttribute(){if(!this.hasAttribute("current-step"))return;const t=Number(this.getAttribute("current-step")||0);Number.isFinite(t)&&(this.internalStep=Math.max(0,t))}isOpenRequested(){return this.hasAttribute("open")&&"false"!==this.getAttribute("open")}getCurrentStepData(){const t=this.parseSteps();if(!t.length)return;const e=Math.min(Math.max(this.internalStep,0),t.length-1);return{step:t[e],index:e,steps:t}}syncOpenState(){if(this.isOpenRequested())return this.mount(),void this.attachListeners();this.detachListeners(),this.clearTargetHighlight(),this.unmount()}mount(){this.root||(this.root=document.createElement("div"),this.root.className=s,this.root.setAttribute("role","dialog"),this.root.setAttribute("aria-modal","true"),this.root.tabIndex=-1,this.ensureStructure()),this.root.isConnected||document.body.append(this.root),this.isMounted=!0,this.render(),this.popoverEl&&!this.focusTrap&&(this.focusTrap=i(this.popoverEl),requestAnimationFrame(()=>this.focusTrap?.activate()))}unmount(){this.isMounted=!1,this.focusTrap?.deactivate(),this.focusTrap=void 0,this.root?.remove()}ensureStructure(){if(!this.root||this.popoverEl)return;this.overlayEl=document.createElement("div"),this.overlayEl.className=`${s}__overlay`,this.overlayEl.addEventListener("click",()=>this.handleEnd()),this.popoverEl=document.createElement("div"),this.popoverEl.className=`${s}__popover`,this.closeButtonEl=document.createElement("button"),this.closeButtonEl.type="button",this.closeButtonEl.className=`${s}__close-button`,this.closeButtonEl.setAttribute("aria-label",t("튜토리얼 종료")),this.closeButtonEl.append(e("close",16)),this.closeButtonEl.addEventListener("click",()=>this.handleEnd());const i=document.createElement("div");i.className=`${s}__content`;const n=document.createElement("div");n.className=`${s}__text`,this.titleEl=document.createElement("h3"),this.titleEl.id="tutorial-title",this.titleEl.className=`${s}__title`,this.descriptionEl=document.createElement("p"),this.descriptionEl.id="tutorial-content",this.descriptionEl.className=`${s}__description`,n.append(this.titleEl,this.descriptionEl);const r=document.createElement("div");r.className=`${s}__navigation`,this.prevButtonEl=document.createElement("db-button"),this.prevButtonEl.setAttribute("variant","secondary"),this.prevButtonEl.setAttribute("size","s"),this.prevButtonEl.setAttribute("start-icon","chevron-left"),this.prevButtonEl.textContent=t("이전"),this.prevButtonEl.addEventListener("db-click",()=>this.handlePrev()),this.indicatorEl=document.createElement("db-indicator"),this.indicatorEl.addEventListener("db-step-click",t=>{const e=t.detail;"number"==typeof e.index&&this.goToStep(e.index)}),this.nextButtonEl=document.createElement("db-button"),this.nextButtonEl.setAttribute("variant","primary"),this.nextButtonEl.setAttribute("size","s"),this.nextButtonEl.addEventListener("db-click",()=>this.handleNext()),r.append(this.prevButtonEl,this.indicatorEl,this.nextButtonEl),i.append(n,r),this.popoverEl.append(this.closeButtonEl,i),this.root.append(this.overlayEl,this.popoverEl)}attachListeners(){this.keyHandler||(this.keyHandler=t=>{"Escape"===t.key?this.handleEnd():"ArrowLeft"===t.key?this.handlePrev():"ArrowRight"===t.key&&this.handleNext()},document.addEventListener("keydown",this.keyHandler)),this.resizeHandler||(this.resizeHandler=()=>this.updateTargetElement(),window.addEventListener("resize",this.resizeHandler))}detachListeners(){this.keyHandler&&(document.removeEventListener("keydown",this.keyHandler),this.keyHandler=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null)}clearTargetHighlight(){this.targetElement&&(this.targetElement.classList.remove(`${s}__target`),this.targetElement.style.position="",this.targetElement.style.zIndex="",this.targetElement=null)}updateTargetElement(){const t=this.getCurrentStepData();if(!t?.step.target||!this.popoverEl)return;this.clearTargetHighlight();const e=document.querySelector(t.step.target);if(!e)return;this.targetElement=e,e.style.position="relative",e.style.zIndex="1000",e.classList.add(`${s}__target`);const i=e.getBoundingClientRect(),n=window.innerWidth,r=window.innerHeight,o=300,a=200,h=16;let l="auto"!==t.step.placement&&t.step.placement?t.step.placement:"bottom",d=i.bottom+h,c=i.left+i.width/2-150;if("auto"===t.step.placement)i.top-a-h>0?(l="top",d=i.top-a-h):i.bottom+a+h<r?(l="bottom",d=i.bottom+h):i.left-o-h>0?(l="left",d=i.top+i.height/2-100,c=i.left-o-h):(l="right",d=i.top+i.height/2-100,c=i.right+h);else switch(l){case"top":d=i.top-a-h;break;case"bottom":d=i.bottom+h;break;case"left":d=i.top+i.height/2-100,c=i.left-o-h;break;case"right":d=i.top+i.height/2-100,c=i.right+h}c=Math.max(h,Math.min(c,n-o-h)),d=Math.max(h,Math.min(d,r-a-h)),this.popoverEl.className=[`${s}__popover`,`${s}__popover--${l}`].join(" "),this.popoverEl.style.top=`${d}px`,this.popoverEl.style.left=`${c}px`}emitStepChange(t){this.dispatchEvent(new CustomEvent("db-step-change",{bubbles:!0,composed:!0,detail:{step:t}}))}goToStep(t){const e=this.parseSteps();e.length&&(this.clearTargetHighlight(),this.internalStep=Math.min(Math.max(t,0),e.length-1),this.setAttribute("current-step",String(this.internalStep)),this.emitStepChange(this.internalStep),this.render())}handlePrev(){const t=this.getCurrentStepData();!t||t.index<=0||(this.dispatchEvent(new CustomEvent("db-prev",{bubbles:!0,composed:!0})),this.goToStep(t.index-1))}handleNext(){const t=this.getCurrentStepData();t&&(t.index>=t.steps.length-1?this.handleEnd():(this.dispatchEvent(new CustomEvent("db-next",{bubbles:!0,composed:!0})),this.goToStep(t.index+1)))}handleEnd(){this.dispatchEvent(new CustomEvent("db-end",{bubbles:!0,composed:!0})),this.setAttribute("open","false")}render(){const t=this.getCurrentStepData();if(!t||!this.root)return;const{step:e,index:i,steps:s}=t,n=0===i,r=i===s.length-1;this.titleEl&&(this.titleEl.textContent=e.title||"",this.titleEl.hidden=!e.title),this.descriptionEl&&(this.descriptionEl.textContent=e.content||"",this.descriptionEl.hidden=!e.content),this.prevButtonEl&&(this.prevButtonEl.hidden=n),this.nextButtonEl&&(this.nextButtonEl.textContent=r?"완료":"다음",r?this.nextButtonEl.removeAttribute("end-icon"):this.nextButtonEl.setAttribute("end-icon","chevron-right")),this.indicatorEl&&(this.indicatorEl.setAttribute("current",String(i)),this.indicatorEl.setAttribute("total",String(s.length)),this.indicatorEl.setAttribute("type",this.getAttribute("indicator-type")||"numbers"),this.indicatorEl.setAttribute("size",this.getAttribute("indicator-size")||"m")),this.updateTargetElement()}}customElements.get("db-tutorial")||customElements.define("db-tutorial",DbTutorial);export{DbTutorial};
@@ -0,0 +1,101 @@
1
+ import { TweetCardVariant, TweetCardSize } from '../ui-contracts/dist/components.d.js';
2
+
3
+ interface DbTweetData {
4
+ id?: string;
5
+ text?: string;
6
+ authorName?: string;
7
+ username?: string;
8
+ avatar?: string;
9
+ verified?: boolean;
10
+ createdAt?: string;
11
+ likes?: number;
12
+ retweets?: number;
13
+ replies?: number;
14
+ views?: number;
15
+ mediaUrl?: string;
16
+ tweetUrl?: string;
17
+ }
18
+ declare class DbTweetCard extends HTMLElement {
19
+ static get observedAttributes(): string[];
20
+ private built;
21
+ private abortController;
22
+ private isLiked;
23
+ private localLikes;
24
+ private headerEl?;
25
+ private avatarImg?;
26
+ private avatarFallback?;
27
+ private authorNameEl?;
28
+ private authorHandleEl?;
29
+ private verifiedBadgeEl?;
30
+ private xLogoLink?;
31
+ private bodyEl?;
32
+ private mediaContainer?;
33
+ private mediaImg?;
34
+ private dateEl?;
35
+ private actionsRow?;
36
+ private replyBtn?;
37
+ private replyCountSpan?;
38
+ private retweetBtn?;
39
+ private retweetCountSpan?;
40
+ private likeBtn?;
41
+ private likeCountSpan?;
42
+ private shareBtn?;
43
+ private skeletonEl?;
44
+ private errorEl?;
45
+ private errorTextEl?;
46
+ private retryBtn?;
47
+ get tweetId(): string;
48
+ set tweetId(value: string);
49
+ get authorName(): string;
50
+ set authorName(value: string);
51
+ get username(): string;
52
+ set username(value: string);
53
+ get avatar(): string;
54
+ set avatar(value: string);
55
+ get verified(): boolean;
56
+ set verified(value: boolean);
57
+ get content(): string;
58
+ set content(value: string);
59
+ get date(): string;
60
+ set date(value: string);
61
+ get likes(): number;
62
+ set likes(value: number);
63
+ get retweets(): number;
64
+ set retweets(value: number);
65
+ get replies(): number;
66
+ set replies(value: number);
67
+ get media(): string;
68
+ set media(value: string);
69
+ get href(): string;
70
+ set href(value: string);
71
+ get variant(): TweetCardVariant;
72
+ set variant(value: TweetCardVariant);
73
+ get size(): TweetCardSize;
74
+ set size(value: TweetCardSize);
75
+ get loading(): boolean;
76
+ set loading(value: boolean);
77
+ get error(): string;
78
+ set error(value: string);
79
+ connectedCallback(): void;
80
+ disconnectedCallback(): void;
81
+ attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
82
+ private fetchTweetData;
83
+ private applyTweetPayload;
84
+ private handleReplyClick;
85
+ private handleRetweetClick;
86
+ private handleLikeClick;
87
+ private handleShareClick;
88
+ private handleRetryClick;
89
+ private getTweetUrl;
90
+ private build;
91
+ private updateLikeButton;
92
+ private sync;
93
+ }
94
+ declare global {
95
+ interface HTMLElementTagNameMap {
96
+ 'db-tweet-card': DbTweetCard;
97
+ }
98
+ }
99
+
100
+ export { DbTweetCard };
101
+ export type { DbTweetData };
@@ -0,0 +1 @@
1
+ import{createDbIcon as t}from"./db-icons.js";import{sanitizeHref as e}from"../core/safe-url.js";import{t as i}from"../core/i18n.js";import{coerceToContract as s}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{TWEET_CARD_VARIANTS as a,TWEET_CARD_SIZES as r}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const n="designbase-wc-tweet-card";function formatCount(t){return!Number.isFinite(t)||t<=0?"0":t>=1e6?(t/1e6).toFixed(1).replace(/\.0$/,"")+"M":t>=1e3?(t/1e3).toFixed(1).replace(/\.0$/,"")+"K":String(t)}class DbTweetCard extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.abortController=null,this.isLiked=!1,this.localLikes=0,this.handleReplyClick=t=>{t.stopPropagation(),this.dispatchEvent(new CustomEvent("db-reply",{bubbles:!0,composed:!0,detail:{tweetId:this.tweetId,username:this.username}}))},this.handleRetweetClick=t=>{t.stopPropagation(),this.dispatchEvent(new CustomEvent("db-retweet",{bubbles:!0,composed:!0,detail:{tweetId:this.tweetId,username:this.username}}))},this.handleLikeClick=t=>{t.stopPropagation(),this.isLiked=!this.isLiked,this.localLikes+=this.isLiked?1:-1,this.updateLikeButton(),this.dispatchEvent(new CustomEvent("db-like",{bubbles:!0,composed:!0,detail:{tweetId:this.tweetId,username:this.username,liked:this.isLiked,count:this.localLikes}}))},this.handleShareClick=t=>{t.stopPropagation();const e=this.getTweetUrl();this.dispatchEvent(new CustomEvent("db-share",{bubbles:!0,composed:!0,detail:{tweetId:this.tweetId,url:e}}))},this.handleRetryClick=t=>{t.stopPropagation(),this.tweetId&&this.fetchTweetData(this.tweetId)}}static get observedAttributes(){return["tweet-id","author-name","author-handle","username","author-avatar","avatar","verified","content","text","date","timestamp","likes","retweets","replies","views","media","href","variant","size","loading","error"]}get tweetId(){return this.getAttribute("tweet-id")||this.getAttribute("id")||""}set tweetId(t){this.setAttribute("tweet-id",t)}get authorName(){return this.getAttribute("author-name")||""}set authorName(t){this.setAttribute("author-name",t)}get username(){return(this.getAttribute("author-handle")||this.getAttribute("username")||"").replace(/^@/,"")}set username(t){this.setAttribute("username",t)}get avatar(){return this.getAttribute("author-avatar")||this.getAttribute("avatar")||""}set avatar(t){this.setAttribute("avatar",t)}get verified(){return this.hasAttribute("verified")}set verified(t){this.toggleAttribute("verified",t)}get content(){return this.getAttribute("content")||this.getAttribute("text")||""}set content(t){this.setAttribute("content",t)}get date(){return this.getAttribute("date")||this.getAttribute("timestamp")||""}set date(t){this.setAttribute("date",t)}get likes(){return parseInt(this.getAttribute("likes")||"0",10)||0}set likes(t){this.setAttribute("likes",String(t))}get retweets(){return parseInt(this.getAttribute("retweets")||"0",10)||0}set retweets(t){this.setAttribute("retweets",String(t))}get replies(){return parseInt(this.getAttribute("replies")||"0",10)||0}set replies(t){this.setAttribute("replies",String(t))}get media(){return this.getAttribute("media")||""}set media(t){this.setAttribute("media",t)}get href(){return this.getAttribute("href")||""}set href(t){this.setAttribute("href",t)}get variant(){return s(this.getAttribute("variant"),a,"default")}set variant(t){this.setAttribute("variant",t)}get size(){return s(this.getAttribute("size"),r,"m")}set size(t){this.setAttribute("size",t)}get loading(){return this.hasAttribute("loading")}set loading(t){this.toggleAttribute("loading",t)}get error(){return this.getAttribute("error")||""}set error(t){t?this.setAttribute("error",t):this.removeAttribute("error")}connectedCallback(){this.setAttribute("role","article"),this.built||(this.build(),this.built=!0),this.sync(),!this.tweetId||this.content||this.authorName||this.error||this.fetchTweetData(this.tweetId)}disconnectedCallback(){this.abortController?.abort(),this.replyBtn?.removeEventListener("click",this.handleReplyClick),this.retweetBtn?.removeEventListener("click",this.handleRetweetClick),this.likeBtn?.removeEventListener("click",this.handleLikeClick),this.shareBtn?.removeEventListener("click",this.handleShareClick),this.retryBtn?.removeEventListener("click",this.handleRetryClick)}attributeChangedCallback(t,e,i){this.built&&e!==i&&("tweet-id"!==t||!i||this.content||this.authorName?this.sync():this.fetchTweetData(i))}async fetchTweetData(t){this.abortController?.abort(),this.abortController=new AbortController,this.loading=!0,this.error="",this.sync();try{const e=`https://react-tweet.vercel.app/api/tweet/${t}`,i=await fetch(e,{signal:this.abortController.signal});if(!i.ok)throw new Error("Status "+i.status);const s=await i.json(),a=s.data||s;this.applyTweetPayload(a),this.loading=!1,this.error="",this.sync()}catch(t){if(this.abortController.signal.aborted)return;this.loading=!1,this.error=t instanceof Error?t.message:"Error",this.sync()}}applyTweetPayload(t){t&&(t.user&&(this.authorName=t.user.name||"",this.username=t.user.screen_name||"",this.avatar=t.user.profile_image_url_https||"",this.verified=Boolean(t.user.verified)),this.content=t.text||"",null!=t.favorite_count&&(this.likes=t.favorite_count),null!=t.retweet_count&&(this.retweets=t.retweet_count),null!=t.reply_count&&(this.replies=t.reply_count),t.created_at&&(this.date=t.created_at),t.photos&&t.photos.length>0?this.media=t.photos[0].url||"":t.mediaDetails&&t.mediaDetails.length>0&&(this.media=t.mediaDetails[0].media_url_https||""))}getTweetUrl(){return this.href?e(this.href):this.username&&this.tweetId?`https://x.com/${this.username}/status/${this.tweetId}`:this.tweetId?`https://x.com/i/status/${this.tweetId}`:"https://x.com"}build(){this.skeletonEl=document.createElement("div"),this.skeletonEl.className=`${n}__skeleton`,this.skeletonEl.setAttribute("aria-hidden","true");const e=document.createElement("div");e.className=`${n}__skel-header`;const s=document.createElement("div");s.className=`${n}__skel-avatar`;const a=document.createElement("div");a.className=`${n}__skel-info`;const r=document.createElement("div");r.className=`${n}__skel-line ${n}__skel-line--short`;const h=document.createElement("div");h.className=`${n}__skel-line ${n}__skel-line--xshort`,a.append(r,h),e.append(s,a);const l=document.createElement("div");l.className=`${n}__skel-body`;const o=document.createElement("div");o.className=`${n}__skel-line`;const d=document.createElement("div");d.className=`${n}__skel-line`;const c=document.createElement("div");c.className=`${n}__skel-line ${n}__skel-line--mid`,l.append(o,d,c),this.skeletonEl.append(e,l),this.errorEl=document.createElement("div"),this.errorEl.className=`${n}__error`,this.errorTextEl=document.createElement("span"),this.errorTextEl.className=`${n}__error-text`,this.retryBtn=document.createElement("button"),this.retryBtn.type="button",this.retryBtn.className=`${n}__retry-btn`,this.retryBtn.textContent=i("tweetCard.retry"),this.retryBtn.addEventListener("click",this.handleRetryClick),this.errorEl.append(this.errorTextEl,this.retryBtn),this.headerEl=document.createElement("div"),this.headerEl.className=`${n}__header`;const u=document.createElement("div");u.className=`${n}__user`,this.avatarImg=document.createElement("img"),this.avatarImg.className=`${n}__avatar`,this.avatarImg.alt="",this.avatarImg.setAttribute("aria-hidden","true"),this.avatarFallback=document.createElement("div"),this.avatarFallback.className=`${n}__avatar ${n}__avatar--fallback`,this.avatarFallback.setAttribute("aria-hidden","true");const m=document.createElement("div");m.className=`${n}__meta`;const p=document.createElement("div");p.className=`${n}__name-line`,this.authorNameEl=document.createElement("span"),this.authorNameEl.className=`${n}__author-name`,this.verifiedBadgeEl=document.createElement("span"),this.verifiedBadgeEl.className=`${n}__verified`,this.verifiedBadgeEl.title=i("tweetCard.verified"),this.verifiedBadgeEl.append(t("verified-badge",16)),p.append(this.authorNameEl,this.verifiedBadgeEl),this.authorHandleEl=document.createElement("span"),this.authorHandleEl.className=`${n}__username`,m.append(p,this.authorHandleEl),u.append(this.avatarImg,this.avatarFallback,m),this.xLogoLink=document.createElement("a"),this.xLogoLink.className=`${n}__logo-link`,this.xLogoLink.target="_blank",this.xLogoLink.rel="noopener noreferrer",this.xLogoLink.setAttribute("aria-label",i("tweetCard.viewOnTwitter")),this.xLogoLink.append(t("x-logo",18)),this.headerEl.append(u,this.xLogoLink),this.bodyEl=document.createElement("div"),this.bodyEl.className=`${n}__body`,this.mediaContainer=document.createElement("div"),this.mediaContainer.className=`${n}__media`,this.mediaImg=document.createElement("img"),this.mediaImg.loading="lazy",this.mediaImg.alt="",this.mediaContainer.append(this.mediaImg),this.dateEl=document.createElement("div"),this.dateEl.className=`${n}__date`,this.actionsRow=document.createElement("div"),this.actionsRow.className=`${n}__actions`,this.replyBtn=document.createElement("button"),this.replyBtn.type="button",this.replyBtn.className=`${n}__action-btn ${n}__action-btn--reply`,this.replyBtn.setAttribute("aria-label",i("tweetCard.reply")),this.replyBtn.append(t("comment",16)),this.replyCountSpan=document.createElement("span"),this.replyBtn.append(this.replyCountSpan),this.replyBtn.addEventListener("click",this.handleReplyClick),this.retweetBtn=document.createElement("button"),this.retweetBtn.type="button",this.retweetBtn.className=`${n}__action-btn ${n}__action-btn--retweet`,this.retweetBtn.setAttribute("aria-label",i("tweetCard.retweet")),this.retweetBtn.append(t("repeat",16)),this.retweetCountSpan=document.createElement("span"),this.retweetBtn.append(this.retweetCountSpan),this.retweetBtn.addEventListener("click",this.handleRetweetClick),this.likeBtn=document.createElement("button"),this.likeBtn.type="button",this.likeBtn.className=`${n}__action-btn ${n}__action-btn--like`,this.likeBtn.setAttribute("aria-label",i("tweetCard.like")),this.likeCountSpan=document.createElement("span"),this.likeBtn.append(t("heart",16),this.likeCountSpan),this.likeBtn.addEventListener("click",this.handleLikeClick),this.shareBtn=document.createElement("button"),this.shareBtn.type="button",this.shareBtn.className=`${n}__action-btn ${n}__action-btn--share`,this.shareBtn.setAttribute("aria-label","Share"),this.shareBtn.append(t("share-alt",16)),this.shareBtn.addEventListener("click",this.handleShareClick),this.actionsRow.append(this.replyBtn,this.retweetBtn,this.likeBtn,this.shareBtn),this.append(this.skeletonEl,this.errorEl,this.headerEl,this.bodyEl,this.mediaContainer,this.dateEl,this.actionsRow)}updateLikeButton(){if(!this.likeBtn||!this.likeCountSpan)return;this.likeBtn.classList.toggle(`${n}__action-btn--liked`,this.isLiked);const e=t(this.isLiked?"heart-filled":"heart",16);this.likeBtn.replaceChildren(e,this.likeCountSpan),this.likeCountSpan.textContent=formatCount(this.localLikes)}sync(){this.classList.add(n),a.forEach(t=>this.classList.remove(`${n}--${t}`)),r.forEach(t=>this.classList.remove(`${n}--${t}`)),this.classList.add(`${n}--${this.variant}`),this.classList.add(`${n}--${this.size}`);const t=this.loading,s=Boolean(this.error);this.classList.toggle(`${n}--loading`,t),this.classList.toggle(`${n}--error`,s),this.setAttribute("aria-busy",String(t)),s?this.setAttribute("aria-invalid","true"):this.removeAttribute("aria-invalid"),this.skeletonEl&&(this.skeletonEl.hidden=!t),this.errorEl&&(this.errorEl.hidden=!s||t,this.errorTextEl&&(this.errorTextEl.textContent=this.error||i("tweetCard.error")));const h=!t&&!s;if(this.headerEl&&(this.headerEl.hidden=!h),this.bodyEl&&(this.bodyEl.hidden=!h),this.mediaContainer&&(this.mediaContainer.hidden=!h||!this.media),this.dateEl&&(this.dateEl.hidden=!h||!this.date),this.actionsRow&&(this.actionsRow.hidden=!h),!h)return;this.authorNameEl&&(this.authorNameEl.textContent=this.authorName||this.username||"User"),this.authorHandleEl&&(this.authorHandleEl.textContent=this.username?`@${this.username}`:"",this.authorHandleEl.hidden=!this.username),this.verifiedBadgeEl&&(this.verifiedBadgeEl.hidden=!this.verified);const l=this.avatar;l&&this.avatarImg?(this.avatarImg.src=e(l),this.avatarImg.hidden=!1,this.avatarFallback&&(this.avatarFallback.hidden=!0)):this.avatarFallback&&(this.avatarFallback.textContent=(this.authorName||this.username||"U").charAt(0).toUpperCase(),this.avatarFallback.hidden=!1,this.avatarImg&&(this.avatarImg.hidden=!0));const o=this.getTweetUrl();if(this.xLogoLink&&(this.xLogoLink.href=o),this.bodyEl){const t=this.content;this.bodyEl.replaceChildren(...function parseTweetContentNodes(t){const i=[],s=/(https?:\/\/[^\s]+)|(@[a-zA-Z0-9_]+)|(#[a-zA-Z0-9_\u0080-\uffff]+)/g;let a,r=0;for(;null!==(a=s.exec(t));){a.index>r&&i.push(document.createTextNode(t.slice(r,a.index)));const s=a[0],h=document.createElement("a");h.className=`${n}__link`,h.textContent=s,h.target="_blank",h.rel="noopener noreferrer",s.startsWith("http")?h.href=e(s):s.startsWith("@")?h.href=e(`https://x.com/${s.slice(1)}`):s.startsWith("#")&&(h.href=e(`https://x.com/hashtag/${s.slice(1)}`)),i.push(h),r=a.index+s.length}return r<t.length&&i.push(document.createTextNode(t.slice(r))),i}(t))}this.mediaImg&&this.media&&(this.mediaImg.src=e(this.media)),this.dateEl&&this.date&&(this.dateEl.textContent=this.date),this.localLikes=this.likes,this.replyCountSpan&&(this.replyCountSpan.textContent=formatCount(this.replies)),this.retweetCountSpan&&(this.retweetCountSpan.textContent=formatCount(this.retweets)),this.updateLikeButton()}}customElements.get("db-tweet-card")||customElements.define("db-tweet-card",DbTweetCard);export{DbTweetCard};
@@ -2,26 +2,20 @@ import './db-button.js';
2
2
  import './db-input.js';
3
3
  import './db-otp-input.js';
4
4
  import './db-segment-control.js';
5
- import { TWO_FACTOR_SIZES } from '@designbasekorea/ui-contracts';
6
-
7
- /**
8
- * ui-wc — db-two-factor (수동 작성, 자동 동기화 대상 아님)
9
- *
10
- * 2단계 인증(2FA): 인증앱 / SMS / 이메일 방식 + OTP.
11
- */
5
+ import { TWO_FACTOR_SIZES } from '../ui-contracts/dist/components.d.js';
12
6
 
13
7
  type Size = (typeof TWO_FACTOR_SIZES)[number];
14
- declare const METHODS: readonly [{
8
+ declare const METHODS: () => readonly [{
15
9
  readonly value: "app";
16
- readonly label: "인증 앱";
10
+ readonly label: string;
17
11
  }, {
18
12
  readonly value: "sms";
19
13
  readonly label: "SMS";
20
14
  }, {
21
15
  readonly value: "email";
22
- readonly label: "이메일";
16
+ readonly label: string;
23
17
  }];
24
- type Method = (typeof METHODS)[number]['value'];
18
+ type Method = ReturnType<typeof METHODS>[number]['value'];
25
19
  declare class DbTwoFactor extends HTMLElement {
26
20
  static get observedAttributes(): string[];
27
21
  private built;
@@ -1 +1 @@
1
- import{createDbIcon as t}from"./db-icons.js";import"./db-button.js";import"./db-input.js";import"./db-otp-input.js";import"./db-segment-control.js";import{coerceToContract as e}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{TWO_FACTOR_SIZES as i}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const s=[{value:"app",label:"인증 앱"},{value:"sms",label:"SMS"},{value:"email",label:"이메일"}];function el(t,e){const i=document.createElement(t);return e&&(i.className=e),i}class DbTwoFactor extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.timerId=null,this.remaining=0,this.backupMode=!1,this.resendCount=0}static get observedAttributes(){return["method","code","size","disabled","error","error-message","title","description","verify-label","code-length","show-backup","countdown","destination","manual","sending","verifying","auto-submit","resend-label","max-resends"]}get method(){const t=this.getAttribute("method");return"sms"===t||"email"===t||"app"===t?t:"app"}set method(t){this.setAttribute("method",t)}get size(){return e(this.getAttribute("size"),i,"m")}set size(t){this.setAttribute("size",t)}get countdown(){const t=Number(this.getAttribute("countdown")||180);return Number.isFinite(t)&&t>0?t:180}set countdown(t){this.setAttribute("countdown",String(t))}get maxResends(){const t=Number(this.getAttribute("max-resends")||3);return Number.isFinite(t)?Math.max(1,Math.min(5,Math.round(t))):3}connectedCallback(){this.build(),this.sync(),"app"===this.method||this.hasAttribute("manual")||this.startTimer(this.countdown)}disconnectedCallback(){this.clearTimer()}attributeChangedCallback(t,e,i){this.built&&(this.sync(),"method"!==t&&"manual"!==t||e===i||("method"===t&&(this.resendCount=0,this.backupMode=!1),"app"===this.method||this.hasAttribute("manual")?this.clearTimer():this.startTimer(this.countdown)))}restartCountdown(){"app"!==this.method&&this.startTimer(this.countdown)}clearTimer(){this.timerId&&clearInterval(this.timerId),this.timerId=null,this.remaining=0,this.timerEl&&(this.timerEl.textContent="")}startTimer(t){this.clearTimer(),this.remaining=t,this.renderTimer(),this.timerId=setInterval(()=>{this.remaining-=1,this.renderTimer(),this.remaining<=0&&(this.clearTimer(),this.resendBtn.disabled=!1,this.dispatchEvent(new CustomEvent("db-countdown-complete",{bubbles:!0,composed:!0})))},1e3)}renderTimer(){const t=Math.max(0,Math.floor(this.remaining/60)),e=Math.max(0,this.remaining%60);this.timerEl.textContent="app"===this.method?"":`${String(t).padStart(2,"0")}:${String(e).padStart(2,"0")}`,"app"!==this.method?this.timerEl.setAttribute("aria-label",`남은 시간 ${t}분 ${e}초`):this.timerEl.removeAttribute("aria-label");const i=this.resendCount>=this.maxResends;this.resendBtn.disabled="app"===this.method||this.remaining>0||i||this.hasAttribute("disabled"),"app"===this.method?this.resendStatus.textContent="":i?this.resendStatus.textContent="재전송 한도에 도달했습니다. 새 인증을 시작해 주세요.":this.remaining>0?this.resendStatus.textContent=`${String(t).padStart(2,"0")}:${String(e).padStart(2,"0")} 후 재전송 가능 · 남은 횟수 ${this.maxResends-this.resendCount}회`:this.resendStatus.textContent=`재전송 가능 · 남은 횟수 ${this.maxResends-this.resendCount}회`}build(){if(this.built)return;this.built=!0;const e="designbase-wc-two-factor";this.replaceChildren();const i=el("div",`${e}__header`),n=el("div",`${e}__icon`);n.append(t("lock",22));const r=el("div",`${e}__titles`),o=el("h3",`${e}__title`);o.dataset.role="title";const a=el("p",`${e}__description`);a.dataset.role="description",r.append(o,a),i.append(n,r),this.segment=document.createElement("db-segment-control"),this.segment.setAttribute("options",JSON.stringify(s.map(t=>({value:t.value,label:t.label})))),this.segment.setAttribute("full-width",""),this.segment.addEventListener("db-change",t=>{t.stopPropagation();const e=String(t.detail?.value||"app");e!==this.method&&(this.setAttribute("code",""),this.method=e,this.dispatchEvent(new CustomEvent("db-method-change",{detail:{method:e},bubbles:!0,composed:!0})),"app"!==e&&this.dispatchEvent(new CustomEvent("db-send-code",{detail:{method:e,destination:this.getAttribute("destination")||""},bubbles:!0,composed:!0})))}),this.hintEl=el("p",`${e}__hint`),this.hintEl.dataset.role="hint",this.codeHead=el("div",`${e}__code-head`);const h=el("span",`${e}__code-label`);h.textContent="인증 코드",this.timerEl=el("span",`${e}__timer`),this.codeHead.append(h,this.timerEl),this.otpInput=document.createElement("db-otp-input"),this.otpInput.addEventListener("db-change",t=>{t.stopPropagation();const e=String(t.detail?.value||"");this.setAttribute("code",e),this.dispatchEvent(new CustomEvent("db-code-change",{detail:{code:e},bubbles:!0,composed:!0}))}),this.otpInput.addEventListener("db-complete",t=>{t.stopPropagation();const e=String(t.detail?.value||"");this.dispatchEvent(new CustomEvent("db-code-complete",{detail:{code:e,method:this.method},bubbles:!0,composed:!0})),this.hasAttribute("auto-submit")&&this.requestVerify()}),this.channelActions=el("div",`${e}__channel`),this.resendBtn=el("button",`${e}__resend`),this.resendBtn.type="button",this.resendBtn.textContent="코드 재전송",this.resendBtn.addEventListener("click",()=>{this.resendBtn.disabled||(this.resendCount+=1,this.dispatchEvent(new CustomEvent("db-resend",{detail:{method:this.method,destination:this.getAttribute("destination")||""},bubbles:!0,composed:!0})),this.hasAttribute("manual")?this.renderTimer():this.startTimer(this.countdown))}),this.resendStatus=el("p",`${e}__resend-status`),this.resendStatus.setAttribute("aria-live","polite"),this.channelActions.append(this.resendBtn,this.resendStatus),this.backupBlock=el("div",`${e}__backup-block`);const d=el("p",`${e}__hint`);d.textContent="계정 설정 시 저장한 8자리 백업 코드 중 사용하지 않은 코드를 입력하세요.",this.backupInput=document.createElement("db-input"),this.backupInput.setAttribute("label","백업 코드"),this.backupInput.setAttribute("placeholder","XXXX-XXXX"),this.backupInput.setAttribute("autocomplete","one-time-code"),this.backupInput.setAttribute("full-width",""),this.backupInput.addEventListener("db-input",t=>{t.stopPropagation();const e=String(t.detail?.value||"").toUpperCase().replace(/[^A-Z0-9]/g,"").slice(0,8),i=e.length>4?`${e.slice(0,4)}-${e.slice(4)}`:e;this.backupInput.value=i,this.sync()}),this.backupCancelBtn=el("button",`${e}__backup`),this.backupCancelBtn.type="button",this.backupCancelBtn.textContent="인증 코드로 돌아가기",this.backupCancelBtn.addEventListener("click",()=>{this.backupMode=!1,this.sync()}),this.backupBlock.append(d,this.backupInput,this.backupCancelBtn),this.verifyBtn=document.createElement("db-button"),this.verifyBtn.setAttribute("variant","primary"),this.verifyBtn.setAttribute("full-width",""),this.verifyBtn.addEventListener("db-click",t=>{t.stopPropagation(),this.requestVerify()}),this.backupBtn=el("button",`${e}__backup`),this.backupBtn.type="button",this.backupBtn.textContent="백업 코드로 인증",this.backupBtn.addEventListener("click",()=>{this.backupMode=!0,this.sync(),requestAnimationFrame(()=>this.backupInput?.focus?.()),this.dispatchEvent(new CustomEvent("db-backup-request",{bubbles:!0,composed:!0}))}),this.errorEl=el("p",`${e}__error`),this.errorEl.hidden=!0,this.append(i,this.segment,this.hintEl,this.codeHead,this.otpInput,this.channelActions,this.backupBlock,this.verifyBtn,this.backupBtn,this.errorEl)}sync(){const t="designbase-wc-two-factor",e=this.size,i=this.method,s=this.hasAttribute("disabled"),n=this.getAttribute("code-length")||"6",r=this.getAttribute("destination")||"";this.className=[t,`${t}--${e}`,`${t}--${i}`,s&&`${t}--disabled`,this.hasAttribute("error")&&`${t}--error`].filter(Boolean).join(" ");const o=this.querySelector('[data-role="title"]'),a=this.querySelector('[data-role="description"]');o.textContent=this.getAttribute("title")||"2단계 인증",a.textContent=this.getAttribute("description")||"계속하려면 인증 코드를 입력해 주세요.",this.segment.setAttribute("value",i),this.segment.setAttribute("size",e),this.segment.toggleAttribute("disabled",s),this.segment.toggleAttribute("hidden",this.backupMode);const h=this.querySelector('[data-role="hint"]');h.textContent="app"===i?"인증 앱(Google Authenticator, 1Password 등)에 표시된 6자리 코드를 입력하세요.":"sms"===i?r?`${r} 으로 보낸 SMS 인증 코드를 입력하세요.`:"등록된 휴대폰으로 보낸 SMS 인증 코드를 입력하세요.":r?`${r} 으로 보낸 이메일 인증 코드를 입력하세요.`:"등록된 이메일로 보낸 인증 코드를 입력하세요.",this.otpInput.setAttribute("length",n),this.otpInput.setAttribute("size",e),this.otpInput.setAttribute("value",this.getAttribute("code")||""),this.otpInput.toggleAttribute("disabled",s),this.otpInput.toggleAttribute("error",this.hasAttribute("error")),this.getAttribute("error-message")?this.otpInput.setAttribute("error-message",this.getAttribute("error-message")||""):this.otpInput.removeAttribute("error-message"),this.hintEl.hidden=this.backupMode,this.codeHead.hidden=this.backupMode,this.otpInput.hidden=this.backupMode,this.channelActions.hidden="app"===i||this.backupMode,this.backupBlock.hidden=!this.backupMode,this.backupInput.setAttribute("size",e),this.backupInput.toggleAttribute("disabled",s),this.resendBtn.textContent=this.hasAttribute("sending")?"전송 중…":this.getAttribute("resend-label")||"코드 재전송",this.renderTimer(),this.hasAttribute("sending")&&(this.resendBtn.disabled=!0),this.verifyBtn.setAttribute("size",e),this.verifyBtn.textContent=this.backupMode?"백업 코드로 인증":this.getAttribute("verify-label")||"인증하기";const d=this.getAttribute("code")||"",u=String(this.backupInput.value||"").replace(/-/g,"");this.verifyBtn.toggleAttribute("disabled",s||(this.backupMode?8!==u.length:d.length<Number(n))),this.verifyBtn.toggleAttribute("loading",this.hasAttribute("verifying")),this.backupBtn.hidden=!this.hasAttribute("show-backup")||this.backupMode;const c=this.getAttribute("error-message")||"";this.errorEl.textContent=c,this.errorEl.hidden=!this.hasAttribute("error")||!c}requestVerify(){if(this.backupMode){const t=String(this.backupInput.value||"").replace(/-/g,"");if(8!==t.length||this.hasAttribute("disabled"))return;return void this.dispatchEvent(new CustomEvent("db-verify",{detail:{method:"backup",code:t},bubbles:!0,composed:!0}))}const t=(this.getAttribute("code")||this.otpInput.value).replace(/\D/g,""),e=Number(this.getAttribute("code-length")||6);t.length!==e||this.hasAttribute("disabled")||this.dispatchEvent(new CustomEvent("db-verify",{detail:{method:this.method,code:t},bubbles:!0,composed:!0}))}}customElements.get("db-two-factor")||customElements.define("db-two-factor",DbTwoFactor);export{DbTwoFactor};
1
+ import{dbFormat as t,dbText as e}from"../core/i18n.js";import{createDbIcon as i}from"./db-icons.js";import"./db-button.js";import"./db-input.js";import"./db-otp-input.js";import"./db-segment-control.js";import{coerceToContract as s}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{TWO_FACTOR_SIZES as n}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";function el(t,e){const i=document.createElement(t);return e&&(i.className=e),i}class DbTwoFactor extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.timerId=null,this.remaining=0,this.backupMode=!1,this.resendCount=0}static get observedAttributes(){return["method","code","size","disabled","error","error-message","title","description","verify-label","code-length","show-backup","countdown","destination","manual","sending","verifying","auto-submit","resend-label","max-resends"]}get method(){const t=this.getAttribute("method");return"sms"===t||"email"===t||"app"===t?t:"app"}set method(t){this.setAttribute("method",t)}get size(){return s(this.getAttribute("size"),n,"m")}set size(t){this.setAttribute("size",t)}get countdown(){const t=Number(this.getAttribute("countdown")||180);return Number.isFinite(t)&&t>0?t:180}set countdown(t){this.setAttribute("countdown",String(t))}get maxResends(){const t=Number(this.getAttribute("max-resends")||3);return Number.isFinite(t)?Math.max(1,Math.min(5,Math.round(t))):3}connectedCallback(){this.build(),this.sync(),"app"===this.method||this.hasAttribute("manual")||this.startTimer(this.countdown)}disconnectedCallback(){this.clearTimer()}attributeChangedCallback(t,e,i){this.built&&(this.sync(),"method"!==t&&"manual"!==t||e===i||("method"===t&&(this.resendCount=0,this.backupMode=!1),"app"===this.method||this.hasAttribute("manual")?this.clearTimer():this.startTimer(this.countdown)))}restartCountdown(){"app"!==this.method&&this.startTimer(this.countdown)}clearTimer(){this.timerId&&clearInterval(this.timerId),this.timerId=null,this.remaining=0,this.timerEl&&(this.timerEl.textContent="")}startTimer(t){this.clearTimer(),this.remaining=t,this.renderTimer(),this.timerId=setInterval(()=>{this.remaining-=1,this.renderTimer(),this.remaining<=0&&(this.clearTimer(),this.resendBtn.disabled=!1,this.dispatchEvent(new CustomEvent("db-countdown-complete",{bubbles:!0,composed:!0})))},1e3)}renderTimer(){const i=Math.max(0,Math.floor(this.remaining/60)),s=Math.max(0,this.remaining%60);this.timerEl.textContent="app"===this.method?"":`${String(i).padStart(2,"0")}:${String(s).padStart(2,"0")}`,"app"!==this.method?this.timerEl.setAttribute("aria-label",t("남은 시간 {0}분 {1}초",[i,s])):this.timerEl.removeAttribute("aria-label");const n=this.resendCount>=this.maxResends;this.resendBtn.disabled="app"===this.method||this.remaining>0||n||this.hasAttribute("disabled"),"app"===this.method?this.resendStatus.textContent="":n?this.resendStatus.textContent=e("재전송 한도에 도달했습니다. 새 인증을 시작해 주세요."):this.remaining>0?this.resendStatus.textContent=t("{0}:{1} 후 재전송 가능 · 남은 횟수 {2}회",[String(i).padStart(2,"0"),String(s).padStart(2,"0"),this.maxResends-this.resendCount]):this.resendStatus.textContent=t("재전송 가능 · 남은 횟수 {0}회",[this.maxResends-this.resendCount])}build(){if(this.built)return;this.built=!0;const t="designbase-wc-two-factor";this.replaceChildren();const s=el("div",`${t}__header`),n=el("div",`${t}__icon`);n.append(i("lock",22));const r=el("div",`${t}__titles`),o=el("h3",`${t}__title`);o.dataset.role="title";const a=el("p",`${t}__description`);a.dataset.role="description",r.append(o,a),s.append(n,r),this.segment=document.createElement("db-segment-control"),this.segment.setAttribute("options",JSON.stringify([{value:"app",label:e("인증 앱")},{value:"sms",label:"SMS"},{value:"email",label:e("이메일")}].map(t=>({value:t.value,label:t.label})))),this.segment.setAttribute("full-width",""),this.segment.addEventListener("db-change",t=>{t.stopPropagation();const e=String(t.detail?.value||"app");e!==this.method&&(this.setAttribute("code",""),this.method=e,this.dispatchEvent(new CustomEvent("db-method-change",{detail:{method:e},bubbles:!0,composed:!0})),"app"!==e&&this.dispatchEvent(new CustomEvent("db-send-code",{detail:{method:e,destination:this.getAttribute("destination")||""},bubbles:!0,composed:!0})))}),this.hintEl=el("p",`${t}__hint`),this.hintEl.dataset.role="hint",this.codeHead=el("div",`${t}__code-head`);const h=el("span",`${t}__code-label`);h.textContent=e("인증 코드"),this.timerEl=el("span",`${t}__timer`),this.codeHead.append(h,this.timerEl),this.otpInput=document.createElement("db-otp-input"),this.otpInput.addEventListener("db-change",t=>{t.stopPropagation();const e=String(t.detail?.value||"");this.setAttribute("code",e),this.dispatchEvent(new CustomEvent("db-code-change",{detail:{code:e},bubbles:!0,composed:!0}))}),this.otpInput.addEventListener("db-complete",t=>{t.stopPropagation();const e=String(t.detail?.value||"");this.dispatchEvent(new CustomEvent("db-code-complete",{detail:{code:e,method:this.method},bubbles:!0,composed:!0})),this.hasAttribute("auto-submit")&&this.requestVerify()}),this.channelActions=el("div",`${t}__channel`),this.resendBtn=el("button",`${t}__resend`),this.resendBtn.type="button",this.resendBtn.textContent=e("코드 재전송"),this.resendBtn.addEventListener("click",()=>{this.resendBtn.disabled||(this.resendCount+=1,this.dispatchEvent(new CustomEvent("db-resend",{detail:{method:this.method,destination:this.getAttribute("destination")||""},bubbles:!0,composed:!0})),this.hasAttribute("manual")?this.renderTimer():this.startTimer(this.countdown))}),this.resendStatus=el("p",`${t}__resend-status`),this.resendStatus.setAttribute("aria-live","polite"),this.channelActions.append(this.resendBtn,this.resendStatus),this.backupBlock=el("div",`${t}__backup-block`);const d=el("p",`${t}__hint`);d.textContent=e("계정 설정 시 저장한 8자리 백업 코드 중 사용하지 않은 코드를 입력하세요."),this.backupInput=document.createElement("db-input"),this.backupInput.setAttribute("label","백업 코드"),this.backupInput.setAttribute("placeholder","XXXX-XXXX"),this.backupInput.setAttribute("autocomplete","one-time-code"),this.backupInput.setAttribute("full-width",""),this.backupInput.addEventListener("db-input",t=>{t.stopPropagation();const e=String(t.detail?.value||"").toUpperCase().replace(/[^A-Z0-9]/g,"").slice(0,8),i=e.length>4?`${e.slice(0,4)}-${e.slice(4)}`:e;this.backupInput.value=i,this.sync()}),this.backupCancelBtn=el("button",`${t}__backup`),this.backupCancelBtn.type="button",this.backupCancelBtn.textContent=e("인증 코드로 돌아가기"),this.backupCancelBtn.addEventListener("click",()=>{this.backupMode=!1,this.sync()}),this.backupBlock.append(d,this.backupInput,this.backupCancelBtn),this.verifyBtn=document.createElement("db-button"),this.verifyBtn.setAttribute("variant","primary"),this.verifyBtn.setAttribute("full-width",""),this.verifyBtn.addEventListener("db-click",t=>{t.stopPropagation(),this.requestVerify()}),this.backupBtn=el("button",`${t}__backup`),this.backupBtn.type="button",this.backupBtn.textContent=e("백업 코드로 인증"),this.backupBtn.addEventListener("click",()=>{this.backupMode=!0,this.sync(),requestAnimationFrame(()=>this.backupInput?.focus?.()),this.dispatchEvent(new CustomEvent("db-backup-request",{bubbles:!0,composed:!0}))}),this.errorEl=el("p",`${t}__error`),this.errorEl.hidden=!0,this.append(s,this.segment,this.hintEl,this.codeHead,this.otpInput,this.channelActions,this.backupBlock,this.verifyBtn,this.backupBtn,this.errorEl)}sync(){const t="designbase-wc-two-factor",i=this.size,s=this.method,n=this.hasAttribute("disabled"),r=this.getAttribute("code-length")||"6",o=this.getAttribute("destination")||"";this.className=[t,`${t}--${i}`,`${t}--${s}`,n&&`${t}--disabled`,this.hasAttribute("error")&&`${t}--error`].filter(Boolean).join(" ");const a=this.querySelector('[data-role="title"]'),h=this.querySelector('[data-role="description"]');a.textContent=this.getAttribute("title")||"2단계 인증",h.textContent=this.getAttribute("description")||"계속하려면 인증 코드를 입력해 주세요.",this.segment.setAttribute("value",s),this.segment.setAttribute("size",i),this.segment.toggleAttribute("disabled",n),this.segment.toggleAttribute("hidden",this.backupMode);const d=this.querySelector('[data-role="hint"]');d.textContent="app"===s?e("인증 앱(Google Authenticator, 1Password 등)에 표시된 6자리 코드를 입력하세요."):"sms"===s?o?`${o} 으로 보낸 SMS 인증 코드를 입력하세요.`:"등록된 휴대폰으로 보낸 SMS 인증 코드를 입력하세요.":o?`${o} 으로 보낸 이메일 인증 코드를 입력하세요.`:"등록된 이메일로 보낸 인증 코드를 입력하세요.",this.otpInput.setAttribute("length",r),this.otpInput.setAttribute("size",i),this.otpInput.setAttribute("value",this.getAttribute("code")||""),this.otpInput.toggleAttribute("disabled",n),this.otpInput.toggleAttribute("error",this.hasAttribute("error")),this.getAttribute("error-message")?this.otpInput.setAttribute("error-message",this.getAttribute("error-message")||""):this.otpInput.removeAttribute("error-message"),this.hintEl.hidden=this.backupMode,this.codeHead.hidden=this.backupMode,this.otpInput.hidden=this.backupMode,this.channelActions.hidden="app"===s||this.backupMode,this.backupBlock.hidden=!this.backupMode,this.backupInput.setAttribute("size",i),this.backupInput.toggleAttribute("disabled",n),this.resendBtn.textContent=this.hasAttribute("sending")?"전송 중…":this.getAttribute("resend-label")||"코드 재전송",this.renderTimer(),this.hasAttribute("sending")&&(this.resendBtn.disabled=!0),this.verifyBtn.setAttribute("size",i),this.verifyBtn.textContent=this.backupMode?"백업 코드로 인증":this.getAttribute("verify-label")||"인증하기";const u=this.getAttribute("code")||"",c=String(this.backupInput.value||"").replace(/-/g,"");this.verifyBtn.toggleAttribute("disabled",n||(this.backupMode?8!==c.length:u.length<Number(r))),this.verifyBtn.toggleAttribute("loading",this.hasAttribute("verifying")),this.backupBtn.hidden=!this.hasAttribute("show-backup")||this.backupMode;const l=this.getAttribute("error-message")||"";this.errorEl.textContent=l,this.errorEl.hidden=!this.hasAttribute("error")||!l}requestVerify(){if(this.backupMode){const t=String(this.backupInput.value||"").replace(/-/g,"");if(8!==t.length||this.hasAttribute("disabled"))return;return void this.dispatchEvent(new CustomEvent("db-verify",{detail:{method:"backup",code:t},bubbles:!0,composed:!0}))}const t=(this.getAttribute("code")||this.otpInput.value).replace(/\D/g,""),e=Number(this.getAttribute("code-length")||6);t.length!==e||this.hasAttribute("disabled")||this.dispatchEvent(new CustomEvent("db-verify",{detail:{method:this.method,code:t},bubbles:!0,composed:!0}))}}customElements.get("db-two-factor")||customElements.define("db-two-factor",DbTwoFactor);export{DbTwoFactor};
@@ -1 +1 @@
1
- import{createDbIcon as t}from"./db-icons.js";import{t as e}from"../core/i18n.js";import{coerceToContract as s}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{VIDEO_PLAYER_SIZES as i,VIDEO_PLAYER_VARIANTS as l}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const o="designbase-wc-video-player",n=20;class DbVideoPlayer extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.rafId=null,this.playlistIndex=0,this.isFullscreen=!1,this.showControlsOverlay=!1,this.isPlaying=!1,this.isMuted=!1,this.volume=1,this.lastVolumeBeforeMute=1,this.currentTime=0,this.duration=0,this.isLoading=!1,this.error=null,this.onFullscreenChange=()=>{this.isFullscreen=document.fullscreenElement===this,this.dispatchEvent(new CustomEvent("db-fullscreen-change",{detail:{isFullscreen:this.isFullscreen},bubbles:!0,composed:!0})),this.updateUI()},this.onMouseEnter=()=>{this.showControlsOverlay=!0,this.updateUI()},this.onMouseLeave=()=>{this.showControlsOverlay=!1,this.updateUI()}}static get observedAttributes(){return["src","poster","title","description","size","variant","theme","auto-play","loop","muted","show-controls","enable-fullscreen","show-progress","show-time","show-volume","playlist","current-index"]}connectedCallback(){this.built||(this.built=!0,this.build(),this.bindVideoEvents()),this.syncFromAttributes(),this.updateUI(),document.addEventListener("fullscreenchange",this.onFullscreenChange),this.addEventListener("mouseenter",this.onMouseEnter),this.addEventListener("mouseleave",this.onMouseLeave)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.onFullscreenChange),this.removeEventListener("mouseenter",this.onMouseEnter),this.removeEventListener("mouseleave",this.onMouseLeave),this.stopProgressAnimation()}attributeChangedCallback(t){this.built&&("src"!==t&&"playlist"!==t&&"current-index"!==t?("muted"===t&&this.videoEl&&(this.videoEl.muted=this.getBool("muted")),this.updateUI()):this.loadVideoSource())}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[]}}getCurrentSrc(){const t=this.getPlaylist();if(t.length>0){const e=Number(this.getAttribute("current-index")||this.playlistIndex||0);return t[Math.max(0,Math.min(t.length-1,e))]}return this.getAttribute("src")||""}formatTime(t){if(!Number.isFinite(t)||t<0)return"0:00";return`${Math.floor(t/60)}:${Math.floor(t%60).toString().padStart(2,"0")}`}iconButton(e,s,i,l){const n=document.createElement("button");return n.type="button",n.className=`${o}__control-button`,n.setAttribute("aria-label",i),n.append(t(e,s)),n.addEventListener("click",t=>{t.stopPropagation(),l()}),n}skipButton(t,e){const s=document.createElement("button");return s.type="button",s.className=`${o}__skip-button`,s.textContent=t,s.setAttribute("aria-label",e<0?"10초 되감기":"10초 빨리감기"),s.addEventListener("click",t=>{t.stopPropagation(),this.seekBy(e)}),s}syncFromAttributes(){this.playlistIndex=Number(this.getAttribute("current-index")||0),this.isMuted=this.getBool("muted"),this.volume=this.isMuted?0:1,this.lastVolumeBeforeMute=this.volume>0?this.volume:1,this.loadVideoSource()}loadVideoSource(){if(!this.videoEl)return;const t=this.getCurrentSrc();if(!t)return;const e=new URL(t,window.location.href).href;this.videoEl.src!==e&&(this.videoEl.src=t,this.videoEl.load()),this.videoEl.poster=this.getAttribute("poster")||"",this.videoEl.autoplay=this.getBool("auto-play"),this.videoEl.loop=this.getBool("loop"),this.videoEl.muted=this.getBool("muted"),this.videoEl.playsInline=!0}bindVideoEvents(){const t=this.videoEl;t&&(t.addEventListener("play",()=>{this.isPlaying=!0,this.startProgressAnimation(),this.dispatchEvent(new CustomEvent("db-play",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("pause",()=>{this.isPlaying=!1,this.stopProgressAnimation(),this.dispatchEvent(new CustomEvent("db-pause",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("timeupdate",()=>{this.currentTime=t.currentTime,this.duration=Number.isFinite(t.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||0===t.volume,!this.isMuted&&this.volume>0&&(this.lastVolumeBeforeMute=this.volume),this.dispatchEvent(new CustomEvent("db-volume-change",{detail:{volume:this.volume},bubbles:!0,composed:!0})),this.updateVolumeUI()}),t.addEventListener("ended",()=>{this.isPlaying=!1,this.stopProgressAnimation(),this.dispatchEvent(new CustomEvent("db-ended",{bubbles:!0,composed:!0}));const e=this.getPlaylist(),s=this.playlistIndex+1;e.length>0&&s<e.length&&(this.playlistIndex=s,this.loadVideoSource(),t.play()),this.updateUI()}),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("loadedmetadata",()=>{this.duration=Number.isFinite(t.duration)?t.duration:0,this.updateTime()}),t.addEventListener("canplay",()=>{this.isLoading=!1,this.updateUI()}),t.addEventListener("click",()=>this.togglePlay()))}togglePlay(){this.videoEl&&(this.videoEl.paused?this.videoEl.play():this.videoEl.pause())}toggleMute(){if(this.videoEl){if(this.isMuted)return this.videoEl.muted=!1,void(this.videoEl.volume=this.lastVolumeBeforeMute>0?this.lastVolumeBeforeMute:1);this.videoEl.volume>0&&(this.lastVolumeBeforeMute=this.videoEl.volume),this.videoEl.muted=!0,this.videoEl.volume=0}}seekBy(t){if(!this.videoEl)return;const e=Number.isFinite(this.videoEl.duration)?this.videoEl.duration:0,s=e>0?e:Number.MAX_SAFE_INTEGER,i=Math.max(0,Math.min(s,this.videoEl.currentTime+t));this.videoEl.currentTime=i}toggleFullscreen(){this.getBool("enable-fullscreen",!0)&&(this.isFullscreen?document.exitFullscreen?.():this.requestFullscreen?.())}build(){this.videoEl=document.createElement("video"),this.videoEl.className=`${o}__video`;const t=document.createElement("div");t.className=`${o}__loading`;const s=document.createElement("div");s.className=`${o}__spinner`,t.append(s);const i=document.createElement("div");i.className=`${o}__error`;const l=document.createElement("p");l.textContent="비디오를 재생할 수 없습니다.";const r=document.createElement("button");r.type="button",r.textContent="다시 시도",r.addEventListener("click",()=>{this.error=null,this.loadVideoSource(),this.updateUI()}),i.append(l,r),this.dimOverlayEl=document.createElement("div"),this.dimOverlayEl.className=`${o}__dim-overlay`,this.overlayEl=document.createElement("div"),this.overlayEl.className=`${o}__overlay`;const a=document.createElement("div");a.className=`${o}__top-controls`;const u=document.createElement("div");u.className=`${o}__title`;const h=document.createElement("h3"),d=document.createElement("p");u.append(h,d),a.append(u),this._titleEl=h,this._descEl=d;const m=document.createElement("div");m.className=`${o}__center-controls`;const c=this.skipButton("-10",-10);this.centerPlayEl=document.createElement("button"),this.centerPlayEl.type="button",this.centerPlayEl.className=`${o}__play-button`,this.centerPlayEl.addEventListener("click",t=>{t.stopPropagation(),this.togglePlay()});const E=this.skipButton("+10",10);m.append(c,this.centerPlayEl,E);const p=document.createElement("div");p.className=`${o}__bottom-controls`,this.progressEl=document.createElement("div"),this.progressEl.className=`${o}__progress`,this.progressBarEl=document.createElement("div"),this.progressBarEl.className=`${o}__progress-bar`,this.progressHandleEl=document.createElement("div"),this.progressHandleEl.className=`${o}__progress-handle`,this.progressEl.append(this.progressBarEl,this.progressHandleEl),this.progressEl.addEventListener("click",t=>{if(!this.videoEl||!Number.isFinite(this.duration)||this.duration<=0)return;const e=this.progressEl.getBoundingClientRect(),s=(t.clientX-e.left)/e.width;this.videoEl.currentTime=s*this.duration});const v=document.createElement("div");v.className=`${o}__controls`;const g=document.createElement("div");g.className=`${o}__left-controls`,this.togglePlayEl=this.iconButton("play",n,e("media.play"),()=>this.togglePlay()),this.volumeWrapEl=document.createElement("div"),this.volumeWrapEl.className=`${o}__volume`,this.muteButtonEl=this.iconButton("volume-up",n,e("media.mute"),()=>this.toggleMute());const b=document.createElement("div");b.className=`${o}__volume-slider`,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.videoEl||!this.volumeInputEl)return;const t=parseFloat(this.volumeInputEl.value);t>0&&(this.lastVolumeBeforeMute=t),this.videoEl.volume=t,this.videoEl.muted=0===t}),b.append(this.volumeInputEl),this.volumeWrapEl.append(this.muteButtonEl,b),this.timeEl=document.createElement("div"),this.timeEl.className=`${o}__time`,this.timeCurrentEl=document.createElement("span");const y=document.createElement("span");y.textContent="/",this.timeDurationEl=document.createElement("span"),this.timeEl.append(this.timeCurrentEl,y,this.timeDurationEl),g.append(this.togglePlayEl,this.volumeWrapEl,this.timeEl);const f=document.createElement("div");f.className=`${o}__right-controls`,this.fullscreenButtonEl=this.iconButton("expand",n,e("media.fullscreen"),()=>this.toggleFullscreen()),f.append(this.fullscreenButtonEl),v.append(g,f),p.append(this.progressEl,v),this.overlayEl.append(a,m,p),this.append(this.videoEl,t,i,this.dimOverlayEl,this.overlayEl)}get titleEl(){return this._titleEl}get descEl(){return this._descEl}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}%`)}updateTime(){this.timeCurrentEl&&(this.timeCurrentEl.textContent=this.formatTime(this.currentTime)),this.timeDurationEl&&(this.timeDurationEl.textContent=this.formatTime(this.duration))}updateVolumeUI(){const e=this.isMuted?0:this.volume;this.volumeInputEl&&(this.volumeInputEl.value=String(e)),this.muteButtonEl&&this.muteButtonEl.replaceChildren(t(this.isMuted||0===e?"mute-filled":"volume-up",n))}startProgressAnimation(){this.stopProgressAnimation();const tick=()=>{!this.videoEl||this.videoEl.paused||this.videoEl.ended||(this.currentTime=this.videoEl.currentTime,this.duration=Number.isFinite(this.videoEl.duration)?this.videoEl.duration:0,this.updateProgress(),this.updateTime(),this.rafId=requestAnimationFrame(tick))};this.rafId=requestAnimationFrame(tick)}stopProgressAnimation(){null!==this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null)}updatePlayIcons(){const e=this.isPlaying?"pause":"play";this.centerPlayEl&&this.centerPlayEl.replaceChildren(t(e,48)),this.togglePlayEl&&this.togglePlayEl.replaceChildren(t(e,n))}updateUI(){const e=s(this.getAttribute("size"),i,"m"),r=s(this.getAttribute("variant"),l,"default"),a=this.getAttribute("theme")||"auto",u=this.getBool("show-controls",!0),h=this.getBool("show-progress",!0),d=this.getBool("show-time",!0),m=this.getBool("show-volume",!0),c=this.getBool("enable-fullscreen",!0);this.className=[o,`${o}--size-${e}`,`${o}--variant-${r}`,`${o}--theme-${a}`,this.isPlaying&&`${o}--playing`,!this.isPlaying&&`${o}--paused`,this.isMuted&&`${o}--muted`,this.isLoading&&`${o}--loading`,this.error&&`${o}--error`,u&&this.showControlsOverlay&&`${o}--show-controls`,this.isFullscreen&&`${o}--fullscreen`].filter(Boolean).join(" ");const E=this.getAttribute("title"),p=this.getAttribute("description");this.titleEl&&(E?(this.titleEl.textContent=E,this.titleEl.hidden=!1):this.titleEl.hidden=!0),this.descEl&&(p?(this.descEl.textContent=p,this.descEl.hidden=!1):this.descEl.hidden=!0),this.overlayEl&&(this.overlayEl.hidden=!u),this.dimOverlayEl&&(this.dimOverlayEl.hidden=!u),this.progressEl&&(this.progressEl.hidden=!h),this.timeEl&&(this.timeEl.hidden=!d),this.volumeWrapEl&&(this.volumeWrapEl.hidden=!m),this.fullscreenButtonEl&&(this.fullscreenButtonEl.hidden=!c),this.fullscreenButtonEl&&this.fullscreenButtonEl.replaceChildren(t(this.isFullscreen?"shrink":"expand",n)),this.updatePlayIcons(),this.updateProgress(),this.updateTime(),this.updateVolumeUI()}}customElements.get("db-video-player")||customElements.define("db-video-player",DbVideoPlayer);export{DbVideoPlayer};
1
+ import{dbText as t,t as e}from"../core/i18n.js";import{createDbIcon as s}from"./db-icons.js";import{coerceToContract as i}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{VIDEO_PLAYER_SIZES as l,VIDEO_PLAYER_VARIANTS as o}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const n="designbase-wc-video-player",r=20;class DbVideoPlayer extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.rafId=null,this.playlistIndex=0,this.isFullscreen=!1,this.showControlsOverlay=!1,this.isPlaying=!1,this.isMuted=!1,this.volume=1,this.lastVolumeBeforeMute=1,this.currentTime=0,this.duration=0,this.isLoading=!1,this.error=null,this.onFullscreenChange=()=>{this.isFullscreen=document.fullscreenElement===this,this.dispatchEvent(new CustomEvent("db-fullscreen-change",{detail:{isFullscreen:this.isFullscreen},bubbles:!0,composed:!0})),this.updateUI()},this.onMouseEnter=()=>{this.showControlsOverlay=!0,this.updateUI()},this.onMouseLeave=()=>{this.showControlsOverlay=!1,this.updateUI()}}static get observedAttributes(){return["src","poster","title","description","size","variant","theme","auto-play","loop","muted","show-controls","enable-fullscreen","show-progress","show-time","show-volume","playlist","current-index"]}connectedCallback(){this.built||(this.built=!0,this.build(),this.bindVideoEvents()),this.syncFromAttributes(),this.updateUI(),document.addEventListener("fullscreenchange",this.onFullscreenChange),this.addEventListener("mouseenter",this.onMouseEnter),this.addEventListener("mouseleave",this.onMouseLeave)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.onFullscreenChange),this.removeEventListener("mouseenter",this.onMouseEnter),this.removeEventListener("mouseleave",this.onMouseLeave),this.stopProgressAnimation()}attributeChangedCallback(t){this.built&&("src"!==t&&"playlist"!==t&&"current-index"!==t?("muted"===t&&this.videoEl&&(this.videoEl.muted=this.getBool("muted")),this.updateUI()):this.loadVideoSource())}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[]}}getCurrentSrc(){const t=this.getPlaylist();if(t.length>0){const e=Number(this.getAttribute("current-index")||this.playlistIndex||0);return t[Math.max(0,Math.min(t.length-1,e))]}return this.getAttribute("src")||""}formatTime(t){if(!Number.isFinite(t)||t<0)return"0:00";return`${Math.floor(t/60)}:${Math.floor(t%60).toString().padStart(2,"0")}`}iconButton(t,e,i,l){const o=document.createElement("button");return o.type="button",o.className=`${n}__control-button`,o.setAttribute("aria-label",i),o.append(s(t,e)),o.addEventListener("click",t=>{t.stopPropagation(),l()}),o}skipButton(t,e){const s=document.createElement("button");return s.type="button",s.className=`${n}__skip-button`,s.textContent=t,s.setAttribute("aria-label",e<0?"10초 되감기":"10초 빨리감기"),s.addEventListener("click",t=>{t.stopPropagation(),this.seekBy(e)}),s}syncFromAttributes(){this.playlistIndex=Number(this.getAttribute("current-index")||0),this.isMuted=this.getBool("muted"),this.volume=this.isMuted?0:1,this.lastVolumeBeforeMute=this.volume>0?this.volume:1,this.loadVideoSource()}loadVideoSource(){if(!this.videoEl)return;const t=this.getCurrentSrc();if(!t)return;const e=new URL(t,window.location.href).href;this.videoEl.src!==e&&(this.videoEl.src=t,this.videoEl.load()),this.videoEl.poster=this.getAttribute("poster")||"",this.videoEl.autoplay=this.getBool("auto-play"),this.videoEl.loop=this.getBool("loop"),this.videoEl.muted=this.getBool("muted"),this.videoEl.playsInline=!0}bindVideoEvents(){const t=this.videoEl;t&&(t.addEventListener("play",()=>{this.isPlaying=!0,this.startProgressAnimation(),this.dispatchEvent(new CustomEvent("db-play",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("pause",()=>{this.isPlaying=!1,this.stopProgressAnimation(),this.dispatchEvent(new CustomEvent("db-pause",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("timeupdate",()=>{this.currentTime=t.currentTime,this.duration=Number.isFinite(t.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||0===t.volume,!this.isMuted&&this.volume>0&&(this.lastVolumeBeforeMute=this.volume),this.dispatchEvent(new CustomEvent("db-volume-change",{detail:{volume:this.volume},bubbles:!0,composed:!0})),this.updateVolumeUI()}),t.addEventListener("ended",()=>{this.isPlaying=!1,this.stopProgressAnimation(),this.dispatchEvent(new CustomEvent("db-ended",{bubbles:!0,composed:!0}));const e=this.getPlaylist(),s=this.playlistIndex+1;e.length>0&&s<e.length&&(this.playlistIndex=s,this.loadVideoSource(),t.play()),this.updateUI()}),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("loadedmetadata",()=>{this.duration=Number.isFinite(t.duration)?t.duration:0,this.updateTime()}),t.addEventListener("canplay",()=>{this.isLoading=!1,this.updateUI()}),t.addEventListener("click",()=>this.togglePlay()))}togglePlay(){this.videoEl&&(this.videoEl.paused?this.videoEl.play():this.videoEl.pause())}toggleMute(){if(this.videoEl){if(this.isMuted)return this.videoEl.muted=!1,void(this.videoEl.volume=this.lastVolumeBeforeMute>0?this.lastVolumeBeforeMute:1);this.videoEl.volume>0&&(this.lastVolumeBeforeMute=this.videoEl.volume),this.videoEl.muted=!0,this.videoEl.volume=0}}seekBy(t){if(!this.videoEl)return;const e=Number.isFinite(this.videoEl.duration)?this.videoEl.duration:0,s=e>0?e:Number.MAX_SAFE_INTEGER,i=Math.max(0,Math.min(s,this.videoEl.currentTime+t));this.videoEl.currentTime=i}toggleFullscreen(){this.getBool("enable-fullscreen",!0)&&(this.isFullscreen?document.exitFullscreen?.():this.requestFullscreen?.())}build(){this.videoEl=document.createElement("video"),this.videoEl.className=`${n}__video`;const s=document.createElement("div");s.className=`${n}__loading`;const i=document.createElement("div");i.className=`${n}__spinner`,s.append(i);const l=document.createElement("div");l.className=`${n}__error`;const o=document.createElement("p");o.textContent=t("비디오를 재생할 수 없습니다.");const a=document.createElement("button");a.type="button",a.textContent=t("다시 시도"),a.addEventListener("click",()=>{this.error=null,this.loadVideoSource(),this.updateUI()}),l.append(o,a),this.dimOverlayEl=document.createElement("div"),this.dimOverlayEl.className=`${n}__dim-overlay`,this.overlayEl=document.createElement("div"),this.overlayEl.className=`${n}__overlay`;const u=document.createElement("div");u.className=`${n}__top-controls`;const h=document.createElement("div");h.className=`${n}__title`;const d=document.createElement("h3"),m=document.createElement("p");h.append(d,m),u.append(h),this._titleEl=d,this._descEl=m;const c=document.createElement("div");c.className=`${n}__center-controls`;const E=this.skipButton("-10",-10);this.centerPlayEl=document.createElement("button"),this.centerPlayEl.type="button",this.centerPlayEl.className=`${n}__play-button`,this.centerPlayEl.addEventListener("click",t=>{t.stopPropagation(),this.togglePlay()});const p=this.skipButton("+10",10);c.append(E,this.centerPlayEl,p);const v=document.createElement("div");v.className=`${n}__bottom-controls`,this.progressEl=document.createElement("div"),this.progressEl.className=`${n}__progress`,this.progressBarEl=document.createElement("div"),this.progressBarEl.className=`${n}__progress-bar`,this.progressHandleEl=document.createElement("div"),this.progressHandleEl.className=`${n}__progress-handle`,this.progressEl.append(this.progressBarEl,this.progressHandleEl),this.progressEl.addEventListener("click",t=>{if(!this.videoEl||!Number.isFinite(this.duration)||this.duration<=0)return;const e=this.progressEl.getBoundingClientRect(),s=(t.clientX-e.left)/e.width;this.videoEl.currentTime=s*this.duration});const g=document.createElement("div");g.className=`${n}__controls`;const b=document.createElement("div");b.className=`${n}__left-controls`,this.togglePlayEl=this.iconButton("play",r,e("media.play"),()=>this.togglePlay()),this.volumeWrapEl=document.createElement("div"),this.volumeWrapEl.className=`${n}__volume`,this.muteButtonEl=this.iconButton("volume-up",r,e("media.mute"),()=>this.toggleMute());const y=document.createElement("div");y.className=`${n}__volume-slider`,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.videoEl||!this.volumeInputEl)return;const t=parseFloat(this.volumeInputEl.value);t>0&&(this.lastVolumeBeforeMute=t),this.videoEl.volume=t,this.videoEl.muted=0===t}),y.append(this.volumeInputEl),this.volumeWrapEl.append(this.muteButtonEl,y),this.timeEl=document.createElement("div"),this.timeEl.className=`${n}__time`,this.timeCurrentEl=document.createElement("span");const f=document.createElement("span");f.textContent="/",this.timeDurationEl=document.createElement("span"),this.timeEl.append(this.timeCurrentEl,f,this.timeDurationEl),b.append(this.togglePlayEl,this.volumeWrapEl,this.timeEl);const _=document.createElement("div");_.className=`${n}__right-controls`,this.fullscreenButtonEl=this.iconButton("expand",r,e("media.fullscreen"),()=>this.toggleFullscreen()),_.append(this.fullscreenButtonEl),g.append(b,_),v.append(this.progressEl,g),this.overlayEl.append(u,c,v),this.append(this.videoEl,s,l,this.dimOverlayEl,this.overlayEl)}get titleEl(){return this._titleEl}get descEl(){return this._descEl}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}%`)}updateTime(){this.timeCurrentEl&&(this.timeCurrentEl.textContent=this.formatTime(this.currentTime)),this.timeDurationEl&&(this.timeDurationEl.textContent=this.formatTime(this.duration))}updateVolumeUI(){const t=this.isMuted?0:this.volume;this.volumeInputEl&&(this.volumeInputEl.value=String(t)),this.muteButtonEl&&this.muteButtonEl.replaceChildren(s(this.isMuted||0===t?"mute-filled":"volume-up",r))}startProgressAnimation(){this.stopProgressAnimation();const tick=()=>{!this.videoEl||this.videoEl.paused||this.videoEl.ended||(this.currentTime=this.videoEl.currentTime,this.duration=Number.isFinite(this.videoEl.duration)?this.videoEl.duration:0,this.updateProgress(),this.updateTime(),this.rafId=requestAnimationFrame(tick))};this.rafId=requestAnimationFrame(tick)}stopProgressAnimation(){null!==this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null)}updatePlayIcons(){const t=this.isPlaying?"pause":"play";this.centerPlayEl&&this.centerPlayEl.replaceChildren(s(t,48)),this.togglePlayEl&&this.togglePlayEl.replaceChildren(s(t,r))}updateUI(){const t=i(this.getAttribute("size"),l,"m"),e=i(this.getAttribute("variant"),o,"default"),a=this.getAttribute("theme")||"auto",u=this.getBool("show-controls",!0),h=this.getBool("show-progress",!0),d=this.getBool("show-time",!0),m=this.getBool("show-volume",!0),c=this.getBool("enable-fullscreen",!0);this.className=[n,`${n}--size-${t}`,`${n}--variant-${e}`,`${n}--theme-${a}`,this.isPlaying&&`${n}--playing`,!this.isPlaying&&`${n}--paused`,this.isMuted&&`${n}--muted`,this.isLoading&&`${n}--loading`,this.error&&`${n}--error`,u&&this.showControlsOverlay&&`${n}--show-controls`,this.isFullscreen&&`${n}--fullscreen`].filter(Boolean).join(" ");const E=this.getAttribute("title"),p=this.getAttribute("description");this.titleEl&&(E?(this.titleEl.textContent=E,this.titleEl.hidden=!1):this.titleEl.hidden=!0),this.descEl&&(p?(this.descEl.textContent=p,this.descEl.hidden=!1):this.descEl.hidden=!0),this.overlayEl&&(this.overlayEl.hidden=!u),this.dimOverlayEl&&(this.dimOverlayEl.hidden=!u),this.progressEl&&(this.progressEl.hidden=!h),this.timeEl&&(this.timeEl.hidden=!d),this.volumeWrapEl&&(this.volumeWrapEl.hidden=!m),this.fullscreenButtonEl&&(this.fullscreenButtonEl.hidden=!c),this.fullscreenButtonEl&&this.fullscreenButtonEl.replaceChildren(s(this.isFullscreen?"shrink":"expand",r)),this.updatePlayIcons(),this.updateProgress(),this.updateTime(),this.updateVolumeUI()}}customElements.get("db-video-player")||customElements.define("db-video-player",DbVideoPlayer);export{DbVideoPlayer};
@@ -0,0 +1,25 @@
1
+ import './db-button.js';
2
+
3
+ type DbVoiceState = 'idle' | 'starting' | 'listening' | 'stopping' | 'completed' | 'cancelled' | 'error' | 'unsupported';
4
+ /** Controlled voice UI only: emits intent, never opens the microphone or uploads audio. */
5
+ declare class DbVoiceControl extends HTMLElement {
6
+ static get observedAttributes(): string[];
7
+ private built;
8
+ private action;
9
+ private cancel;
10
+ private label;
11
+ private status;
12
+ connectedCallback(): void;
13
+ attributeChangedCallback(): void;
14
+ private get active();
15
+ private emit;
16
+ private sync;
17
+ }
18
+ declare global {
19
+ interface HTMLElementTagNameMap {
20
+ 'db-voice-control': DbVoiceControl;
21
+ }
22
+ }
23
+
24
+ export { DbVoiceControl };
25
+ export type { DbVoiceState };
@@ -0,0 +1 @@
1
+ import"./db-button.js";class DbVoiceControl extends HTMLElement{constructor(){super(...arguments),this.built=!1}static get observedAttributes(){return["state","message","disabled","compact","start-label","stop-label","cancel-label"]}connectedCallback(){this.built||(this.built=!0,this.classList.add("designbase-wc-voice-control"),this.action=document.createElement("db-button"),this.label=document.createElement("span"),this.action.append(this.label),this.action.setAttribute("size","m"),this.action.setAttribute("radius","pill"),this.action.addEventListener("db-click",()=>{this.hasAttribute("disabled")||"unsupported"===this.getAttribute("state")||"stopping"===this.getAttribute("state")||this.emit(this.active?"stop":"start")}),this.cancel=document.createElement("db-button"),this.cancel.setAttribute("variant","ghost"),this.cancel.setAttribute("size","s"),this.cancel.setAttribute("start-icon","close"),this.cancel.setAttribute("icon-only",""),this.cancel.addEventListener("db-click",()=>{!this.hasAttribute("disabled")&&this.active&&this.emit("cancel")}),this.status=document.createElement("span"),this.status.setAttribute("role","status"),this.status.setAttribute("aria-live","polite"),this.append(this.action,this.status,this.cancel)),this.sync()}attributeChangedCallback(){this.built&&this.sync()}get active(){return["starting","listening","stopping"].includes(this.getAttribute("state")||"idle")}emit(t){this.dispatchEvent(new CustomEvent(`db-${t}`,{bubbles:!0,composed:!0}))}sync(){const t=this.getAttribute("state")||"idle",e=this.active?this.getAttribute("stop-label")||"받아쓰기 완료":this.getAttribute("start-label")||"음성 입력";this.action.setAttribute("aria-label",e),this.action.setAttribute("aria-pressed",String(this.active)),this.action.setAttribute("variant",this.active?"secondary":"ghost"),this.action.setAttribute("start-icon",this.active?"stop":"microphone"),this.action.toggleAttribute("icon-only",this.hasAttribute("compact")),this.label.textContent=e,this.label.hidden=this.hasAttribute("compact"),this.action.toggleAttribute("disabled",this.hasAttribute("disabled")||"unsupported"===t||"stopping"===t);this.status.textContent=this.getAttribute("message")||{starting:"마이크 연결 중",listening:"음성 인식 중",stopping:"마지막 문장 확인 중",error:"음성 인식 실패",unsupported:"음성 입력 미지원"}[t]||"",this.status.hidden=!this.status.textContent,this.cancel.hidden=!this.active,this.cancel.toggleAttribute("disabled",this.hasAttribute("disabled")),this.cancel.setAttribute("aria-label",this.getAttribute("cancel-label")||"음성 입력 취소"),this.dataset.active=String(this.active)}}customElements.get("db-voice-control")||customElements.define("db-voice-control",DbVoiceControl);export{DbVoiceControl};