@designbasekorea/ui-wc 0.8.1 → 0.8.3
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.
- package/CHANGELOG.md +19 -0
- package/README.md +18 -1
- package/dist/esm/components/animation-background/controls.js +1 -1
- package/dist/esm/components/animation-background/layers.js +1 -1
- package/dist/esm/components/db-accordion.d.ts +1 -1
- package/dist/esm/components/db-activity-heatmap.d.ts +38 -0
- package/dist/esm/components/db-activity-heatmap.js +1 -0
- package/dist/esm/components/db-animation-background.d.ts +2 -0
- package/dist/esm/components/db-animation-background.js +1 -1
- package/dist/esm/components/db-app-badge.d.ts +2 -0
- package/dist/esm/components/db-app-badge.js +1 -1
- package/dist/esm/components/db-audio-player.d.ts +4 -0
- package/dist/esm/components/db-audio-player.js +1 -1
- package/dist/esm/components/db-avatar.js +1 -1
- package/dist/esm/components/db-backdrop.d.ts +4 -0
- package/dist/esm/components/db-backdrop.js +1 -1
- package/dist/esm/components/db-badge.d.ts +2 -2
- package/dist/esm/components/db-badge.js +1 -1
- package/dist/esm/components/db-bottom-navigation.js +1 -1
- package/dist/esm/components/db-bottom-sheet.d.ts +5 -0
- package/dist/esm/components/db-bottom-sheet.js +1 -1
- package/dist/esm/components/db-breadcrumbs.d.ts +1 -1
- package/dist/esm/components/db-button.d.ts +13 -0
- package/dist/esm/components/db-button.js +1 -1
- package/dist/esm/components/db-calendar.js +1 -1
- package/dist/esm/components/db-card.js +1 -1
- package/dist/esm/components/db-carousel.js +1 -1
- package/dist/esm/components/db-chat-composer.d.ts +14 -0
- package/dist/esm/components/db-chat-composer.js +1 -1
- package/dist/esm/components/db-chat-message.d.ts +15 -2
- package/dist/esm/components/db-chat-message.js +1 -1
- package/dist/esm/components/db-chat-orb.d.ts +6 -8
- package/dist/esm/components/db-chat-orb.js +1 -1
- package/dist/esm/components/db-chat-room.d.ts +12 -1
- package/dist/esm/components/db-chat-room.js +1 -1
- package/dist/esm/components/db-chat-welcome.js +1 -1
- package/dist/esm/components/db-chat-widget.d.ts +43 -10
- package/dist/esm/components/db-chat-widget.js +1 -1
- package/dist/esm/components/db-checkbox.d.ts +11 -0
- package/dist/esm/components/db-checkbox.js +1 -1
- package/dist/esm/components/db-chip.js +1 -1
- package/dist/esm/components/db-color-picker.js +1 -1
- package/dist/esm/components/db-confirm.d.ts +5 -0
- package/dist/esm/components/db-confirm.js +1 -1
- package/dist/esm/components/db-container.js +1 -1
- package/dist/esm/components/db-cookie-banner.js +1 -1
- package/dist/esm/components/db-date-picker.d.ts +4 -1
- package/dist/esm/components/db-date-picker.js +1 -1
- package/dist/esm/components/db-divider.js +1 -1
- package/dist/esm/components/db-download-progress.d.ts +3 -4
- package/dist/esm/components/db-download-progress.js +1 -1
- package/dist/esm/components/db-drawer.d.ts +5 -1
- package/dist/esm/components/db-drawer.js +1 -1
- package/dist/esm/components/db-dropdown.js +1 -1
- package/dist/esm/components/db-dropzone.js +1 -1
- package/dist/esm/components/db-email-verify.d.ts +2 -2
- package/dist/esm/components/db-email-verify.js +1 -1
- package/dist/esm/components/db-file-uploader.js +1 -1
- package/dist/esm/components/db-floating-action-button.d.ts +1 -0
- package/dist/esm/components/db-floating-action-button.js +1 -1
- package/dist/esm/components/db-footer.js +1 -1
- package/dist/esm/components/db-gantt.js +1 -1
- package/dist/esm/components/db-indicator.js +1 -1
- package/dist/esm/components/db-input.d.ts +7 -0
- package/dist/esm/components/db-input.js +1 -1
- package/dist/esm/components/db-kanban.js +1 -1
- package/dist/esm/components/db-lightbox.d.ts +4 -0
- package/dist/esm/components/db-lightbox.js +1 -1
- package/dist/esm/components/db-list.js +1 -1
- package/dist/esm/components/db-masonry.js +1 -1
- package/dist/esm/components/db-menu-item.js +1 -1
- package/dist/esm/components/db-modal.d.ts +10 -0
- package/dist/esm/components/db-modal.js +1 -1
- package/dist/esm/components/db-navbar.d.ts +4 -2
- package/dist/esm/components/db-navbar.js +1 -1
- package/dist/esm/components/db-onboarding-modal.d.ts +3 -0
- package/dist/esm/components/db-onboarding-modal.js +1 -1
- package/dist/esm/components/db-otp-input.d.ts +3 -2
- package/dist/esm/components/db-otp-input.js +1 -1
- package/dist/esm/components/db-page-header.js +1 -1
- package/dist/esm/components/db-phone-verify.d.ts +2 -2
- package/dist/esm/components/db-phone-verify.js +1 -1
- package/dist/esm/components/db-popover.js +1 -1
- package/dist/esm/components/db-progress-step.js +1 -1
- package/dist/esm/components/db-progress.js +1 -1
- package/dist/esm/components/db-progressbar.js +1 -1
- package/dist/esm/components/db-radio.d.ts +1 -0
- package/dist/esm/components/db-radio.js +1 -1
- package/dist/esm/components/db-range-slider.d.ts +9 -4
- package/dist/esm/components/db-range-slider.js +1 -1
- package/dist/esm/components/db-rating.js +1 -1
- package/dist/esm/components/db-resizable-panels.d.ts +14 -0
- package/dist/esm/components/db-resizable-panels.js +1 -1
- package/dist/esm/components/db-search-bar.js +1 -1
- package/dist/esm/components/db-section-cta.js +1 -1
- package/dist/esm/components/db-section-feature.js +1 -1
- package/dist/esm/components/db-section-pricing.js +1 -1
- package/dist/esm/components/db-section.js +1 -1
- package/dist/esm/components/db-segment-control.js +1 -1
- package/dist/esm/components/db-select.d.ts +8 -0
- package/dist/esm/components/db-select.js +1 -1
- package/dist/esm/components/db-sidebar.d.ts +41 -6
- package/dist/esm/components/db-sidebar.js +1 -1
- package/dist/esm/components/db-skeleton.js +1 -1
- package/dist/esm/components/db-spinner.d.ts +4 -0
- package/dist/esm/components/db-spinner.js +1 -1
- package/dist/esm/components/db-split-view.js +1 -1
- package/dist/esm/components/db-stat.js +1 -1
- package/dist/esm/components/db-stepper.js +1 -1
- package/dist/esm/components/db-table.js +1 -1
- package/dist/esm/components/db-tabs.d.ts +1 -1
- package/dist/esm/components/db-tabs.js +1 -1
- package/dist/esm/components/db-testimonial.js +1 -1
- package/dist/esm/components/db-textarea.js +1 -1
- package/dist/esm/components/db-time-picker.d.ts +5 -2
- package/dist/esm/components/db-time-picker.js +1 -1
- package/dist/esm/components/db-timeline.js +1 -1
- package/dist/esm/components/db-toast.d.ts +8 -1
- package/dist/esm/components/db-toast.js +1 -1
- package/dist/esm/components/db-toggle.d.ts +2 -0
- package/dist/esm/components/db-toggle.js +1 -1
- package/dist/esm/components/db-tooltip.js +1 -1
- package/dist/esm/components/db-top-banner.js +1 -1
- package/dist/esm/components/db-tutorial.d.ts +1 -0
- package/dist/esm/components/db-tutorial.js +1 -1
- package/dist/esm/components/db-two-factor.d.ts +2 -2
- package/dist/esm/components/db-two-factor.js +1 -1
- package/dist/esm/components/db-video-player.d.ts +3 -0
- package/dist/esm/components/db-video-player.js +1 -1
- package/dist/esm/components/section-block-header-utils.js +1 -1
- package/dist/esm/core/a11y.d.ts +40 -0
- package/dist/esm/core/a11y.js +1 -0
- package/dist/esm/core/base-element.d.ts +41 -0
- package/dist/esm/core/base-element.js +1 -0
- package/dist/esm/core/dom.d.ts +35 -0
- package/dist/esm/core/dom.js +1 -0
- package/dist/esm/core/focus-trap.d.ts +41 -0
- package/dist/esm/core/focus-trap.js +1 -0
- package/dist/esm/core/i18n.d.ts +82 -0
- package/dist/esm/core/i18n.js +1 -0
- package/dist/esm/core/scroll-lock.d.ts +19 -0
- package/dist/esm/core/scroll-lock.js +1 -0
- package/dist/esm/core/uid.d.ts +23 -0
- package/dist/esm/core/uid.js +1 -0
- package/dist/esm/helpers/toast.d.ts +10 -10
- package/dist/esm/helpers/toast.js +1 -1
- package/dist/esm/index.d.ts +12 -4
- package/dist/esm/index.js +1 -1
- package/dist/esm/node_modules/@designbasekorea/ui-contracts/dist/components.js +1 -0
- package/dist/esm/node_modules/@designbasekorea/ui-contracts/dist/primitives.js +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +587 -82
- package/dist/index.esm.js +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +470 -122
- package/dist/react/index.esm.js +1 -1
- package/package.json +37 -10
- package/dist/chunks/lottie-26a5e98f.js +0 -1
- package/dist/esm/components/db-lottie.d.ts +0 -20
- package/dist/esm/components/db-lottie.js +0 -1
- package/dist/react/chunks/lottie-14e7addb.js +0 -1
|
@@ -2,6 +2,7 @@ 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';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* ui-wc — db-two-factor (수동 작성, 자동 동기화 대상 아님)
|
|
@@ -9,6 +10,7 @@ import './db-segment-control.js';
|
|
|
9
10
|
* 2단계 인증(2FA): 인증앱 / SMS / 이메일 방식 + OTP.
|
|
10
11
|
*/
|
|
11
12
|
|
|
13
|
+
type Size = (typeof TWO_FACTOR_SIZES)[number];
|
|
12
14
|
declare const METHODS: readonly [{
|
|
13
15
|
readonly value: "app";
|
|
14
16
|
readonly label: "인증 앱";
|
|
@@ -19,8 +21,6 @@ declare const METHODS: readonly [{
|
|
|
19
21
|
readonly value: "email";
|
|
20
22
|
readonly label: "이메일";
|
|
21
23
|
}];
|
|
22
|
-
declare const SIZES: readonly ["s", "m", "l"];
|
|
23
|
-
type Size = (typeof SIZES)[number];
|
|
24
24
|
type Method = (typeof METHODS)[number]['value'];
|
|
25
25
|
declare class DbTwoFactor extends HTMLElement {
|
|
26
26
|
static get observedAttributes(): string[];
|
|
@@ -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";const e=[{value:"app",label:"인증 앱"},{value:"sms",label:"SMS"},{value:"email",label:"이메일"}],i=["s","m","l"];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 function pick(t,e,i){return t.includes(e)?e:i}(i,this.getAttribute("size"),"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 i="designbase-wc-two-factor";this.replaceChildren();const s=el("div",`${i}__header`),n=el("div",`${i}__icon`);n.append(t("lock",22));const r=el("div",`${i}__titles`),h=el("h3",`${i}__title`);h.dataset.role="title";const a=el("p",`${i}__description`);a.dataset.role="description",r.append(h,a),s.append(n,r),this.segment=document.createElement("db-segment-control"),this.segment.setAttribute("options",JSON.stringify(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",`${i}__hint`),this.hintEl.dataset.role="hint",this.codeHead=el("div",`${i}__code-head`);const o=el("span",`${i}__code-label`);o.textContent="인증 코드",this.timerEl=el("span",`${i}__timer`),this.codeHead.append(o,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",`${i}__channel`),this.resendBtn=el("button",`${i}__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",`${i}__resend-status`),this.resendStatus.setAttribute("aria-live","polite"),this.channelActions.append(this.resendBtn,this.resendStatus),this.backupBlock=el("div",`${i}__backup-block`);const d=el("p",`${i}__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",`${i}__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",`${i}__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",`${i}__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",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 h=this.querySelector('[data-role="title"]'),a=this.querySelector('[data-role="description"]');h.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 o=this.querySelector('[data-role="hint"]');o.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 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
|
+
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{TWO_FACTOR_SIZES as e}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const i=[{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 function pick(t,e,i){return t.includes(e)?e:i}(e,this.getAttribute("size"),"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 s=el("div",`${e}__header`),n=el("div",`${e}__icon`);n.append(t("lock",22));const r=el("div",`${e}__titles`),h=el("h3",`${e}__title`);h.dataset.role="title";const a=el("p",`${e}__description`);a.dataset.role="description",r.append(h,a),s.append(n,r),this.segment=document.createElement("db-segment-control"),this.segment.setAttribute("options",JSON.stringify(i.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 o=el("span",`${e}__code-label`);o.textContent="인증 코드",this.timerEl=el("span",`${e}__timer`),this.codeHead.append(o,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(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",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 h=this.querySelector('[data-role="title"]'),a=this.querySelector('[data-role="description"]');h.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 o=this.querySelector('[data-role="hint"]');o.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};
|
|
@@ -35,6 +35,9 @@ declare class DbVideoPlayer extends HTMLElement {
|
|
|
35
35
|
private getPlaylist;
|
|
36
36
|
private getCurrentSrc;
|
|
37
37
|
private formatTime;
|
|
38
|
+
/**
|
|
39
|
+
* 아이콘만 있는 컨트롤 버튼. `label` 이 유일한 접근 가능한 이름이다. (A11Y-NAME)
|
|
40
|
+
*/
|
|
38
41
|
private iconButton;
|
|
39
42
|
private skipButton;
|
|
40
43
|
private syncFromAttributes;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createDbIcon as t}from"./db-icons.js";const e="designbase-wc-video-player",s=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(s,i,l){const n=document.createElement("button");return n.type="button",n.className=`${e}__control-button`,n.append(t(s,i)),n.addEventListener("click",t=>{t.stopPropagation(),l()}),n}skipButton(t,s){const i=document.createElement("button");return i.type="button",i.className=`${e}__skip-button`,i.textContent=t,i.setAttribute("aria-label",s<0?"10초 되감기":"10초 빨리감기"),i.addEventListener("click",t=>{t.stopPropagation(),this.seekBy(s)}),i}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=`${e}__video`;const t=document.createElement("div");t.className=`${e}__loading`;const i=document.createElement("div");i.className=`${e}__spinner`,t.append(i);const l=document.createElement("div");l.className=`${e}__error`;const n=document.createElement("p");n.textContent="비디오를 재생할 수 없습니다.";const o=document.createElement("button");o.type="button",o.textContent="다시 시도",o.addEventListener("click",()=>{this.error=null,this.loadVideoSource(),this.updateUI()}),l.append(n,o),this.dimOverlayEl=document.createElement("div"),this.dimOverlayEl.className=`${e}__dim-overlay`,this.overlayEl=document.createElement("div"),this.overlayEl.className=`${e}__overlay`;const r=document.createElement("div");r.className=`${e}__top-controls`;const a=document.createElement("div");a.className=`${e}__title`;const h=document.createElement("h3"),u=document.createElement("p");a.append(h,u),r.append(a),this._titleEl=h,this._descEl=u;const d=document.createElement("div");d.className=`${e}__center-controls`;const c=this.skipButton("-10",-10);this.centerPlayEl=document.createElement("button"),this.centerPlayEl.type="button",this.centerPlayEl.className=`${e}__play-button`,this.centerPlayEl.addEventListener("click",t=>{t.stopPropagation(),this.togglePlay()});const m=this.skipButton("+10",10);d.append(c,this.centerPlayEl,m);const E=document.createElement("div");E.className=`${e}__bottom-controls`,this.progressEl=document.createElement("div"),this.progressEl.className=`${e}__progress`,this.progressBarEl=document.createElement("div"),this.progressBarEl.className=`${e}__progress-bar`,this.progressHandleEl=document.createElement("div"),this.progressHandleEl.className=`${e}__progress-handle`,this.progressEl.append(this.progressBarEl,this.progressHandleEl),this.progressEl.addEventListener("click",t=>{if(!this.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 p=document.createElement("div");p.className=`${e}__controls`;const v=document.createElement("div");v.className=`${e}__left-controls`,this.togglePlayEl=this.iconButton("play",s,()=>this.togglePlay()),this.volumeWrapEl=document.createElement("div"),this.volumeWrapEl.className=`${e}__volume`,this.muteButtonEl=this.iconButton("volume-up",s,()=>this.toggleMute());const g=document.createElement("div");g.className=`${e}__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}),g.append(this.volumeInputEl),this.volumeWrapEl.append(this.muteButtonEl,g),this.timeEl=document.createElement("div"),this.timeEl.className=`${e}__time`,this.timeCurrentEl=document.createElement("span");const b=document.createElement("span");b.textContent="/",this.timeDurationEl=document.createElement("span"),this.timeEl.append(this.timeCurrentEl,b,this.timeDurationEl),v.append(this.togglePlayEl,this.volumeWrapEl,this.timeEl);const y=document.createElement("div");y.className=`${e}__right-controls`,this.fullscreenButtonEl=this.iconButton("expand",s,()=>this.toggleFullscreen()),y.append(this.fullscreenButtonEl),p.append(v,y),E.append(this.progressEl,p),this.overlayEl.append(r,d,E),this.append(this.videoEl,t,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 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",s))}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,s))}updateUI(){const i=this.getAttribute("size")||"m",l=this.getAttribute("variant")||"default",n=this.getAttribute("theme")||"auto",o=this.getBool("show-controls",!0),r=this.getBool("show-progress",!0),a=this.getBool("show-time",!0),h=this.getBool("show-volume",!0),u=this.getBool("enable-fullscreen",!0);this.className=[e,`${e}--size-${i}`,`${e}--variant-${l}`,`${e}--theme-${n}`,this.isPlaying&&`${e}--playing`,!this.isPlaying&&`${e}--paused`,this.isMuted&&`${e}--muted`,this.isLoading&&`${e}--loading`,this.error&&`${e}--error`,o&&this.showControlsOverlay&&`${e}--show-controls`,this.isFullscreen&&`${e}--fullscreen`].filter(Boolean).join(" ");const d=this.getAttribute("title"),c=this.getAttribute("description");this.titleEl&&(d?(this.titleEl.textContent=d,this.titleEl.hidden=!1):this.titleEl.hidden=!0),this.descEl&&(c?(this.descEl.textContent=c,this.descEl.hidden=!1):this.descEl.hidden=!0),this.overlayEl&&(this.overlayEl.hidden=!o),this.dimOverlayEl&&(this.dimOverlayEl.hidden=!o),this.progressEl&&(this.progressEl.hidden=!r),this.timeEl&&(this.timeEl.hidden=!a),this.volumeWrapEl&&(this.volumeWrapEl.hidden=!h),this.fullscreenButtonEl&&(this.fullscreenButtonEl.hidden=!u),this.fullscreenButtonEl&&this.fullscreenButtonEl.replaceChildren(t(this.isFullscreen?"shrink":"expand",s)),this.updatePlayIcons(),this.updateProgress(),this.updateTime(),this.updateVolumeUI()}}customElements.get("db-video-player")||customElements.define("db-video-player",DbVideoPlayer);export{DbVideoPlayer};
|
|
1
|
+
import{createDbIcon as t}from"./db-icons.js";import{t as e}from"../core/i18n.js";const s="designbase-wc-video-player",i=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,i,l,n){const o=document.createElement("button");return o.type="button",o.className=`${s}__control-button`,o.setAttribute("aria-label",l),o.append(t(e,i)),o.addEventListener("click",t=>{t.stopPropagation(),n()}),o}skipButton(t,e){const i=document.createElement("button");return i.type="button",i.className=`${s}__skip-button`,i.textContent=t,i.setAttribute("aria-label",e<0?"10초 되감기":"10초 빨리감기"),i.addEventListener("click",t=>{t.stopPropagation(),this.seekBy(e)}),i}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=`${s}__video`;const t=document.createElement("div");t.className=`${s}__loading`;const l=document.createElement("div");l.className=`${s}__spinner`,t.append(l);const n=document.createElement("div");n.className=`${s}__error`;const o=document.createElement("p");o.textContent="비디오를 재생할 수 없습니다.";const r=document.createElement("button");r.type="button",r.textContent="다시 시도",r.addEventListener("click",()=>{this.error=null,this.loadVideoSource(),this.updateUI()}),n.append(o,r),this.dimOverlayEl=document.createElement("div"),this.dimOverlayEl.className=`${s}__dim-overlay`,this.overlayEl=document.createElement("div"),this.overlayEl.className=`${s}__overlay`;const a=document.createElement("div");a.className=`${s}__top-controls`;const h=document.createElement("div");h.className=`${s}__title`;const u=document.createElement("h3"),d=document.createElement("p");h.append(u,d),a.append(h),this._titleEl=u,this._descEl=d;const c=document.createElement("div");c.className=`${s}__center-controls`;const m=this.skipButton("-10",-10);this.centerPlayEl=document.createElement("button"),this.centerPlayEl.type="button",this.centerPlayEl.className=`${s}__play-button`,this.centerPlayEl.addEventListener("click",t=>{t.stopPropagation(),this.togglePlay()});const E=this.skipButton("+10",10);c.append(m,this.centerPlayEl,E);const p=document.createElement("div");p.className=`${s}__bottom-controls`,this.progressEl=document.createElement("div"),this.progressEl.className=`${s}__progress`,this.progressBarEl=document.createElement("div"),this.progressBarEl.className=`${s}__progress-bar`,this.progressHandleEl=document.createElement("div"),this.progressHandleEl.className=`${s}__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=`${s}__controls`;const g=document.createElement("div");g.className=`${s}__left-controls`,this.togglePlayEl=this.iconButton("play",i,e("media.play"),()=>this.togglePlay()),this.volumeWrapEl=document.createElement("div"),this.volumeWrapEl.className=`${s}__volume`,this.muteButtonEl=this.iconButton("volume-up",i,e("media.mute"),()=>this.toggleMute());const b=document.createElement("div");b.className=`${s}__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=`${s}__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=`${s}__right-controls`,this.fullscreenButtonEl=this.iconButton("expand",i,e("media.fullscreen"),()=>this.toggleFullscreen()),f.append(this.fullscreenButtonEl),v.append(g,f),p.append(this.progressEl,v),this.overlayEl.append(a,c,p),this.append(this.videoEl,t,n,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",i))}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,i))}updateUI(){const e=this.getAttribute("size")||"m",l=this.getAttribute("variant")||"default",n=this.getAttribute("theme")||"auto",o=this.getBool("show-controls",!0),r=this.getBool("show-progress",!0),a=this.getBool("show-time",!0),h=this.getBool("show-volume",!0),u=this.getBool("enable-fullscreen",!0);this.className=[s,`${s}--size-${e}`,`${s}--variant-${l}`,`${s}--theme-${n}`,this.isPlaying&&`${s}--playing`,!this.isPlaying&&`${s}--paused`,this.isMuted&&`${s}--muted`,this.isLoading&&`${s}--loading`,this.error&&`${s}--error`,o&&this.showControlsOverlay&&`${s}--show-controls`,this.isFullscreen&&`${s}--fullscreen`].filter(Boolean).join(" ");const d=this.getAttribute("title"),c=this.getAttribute("description");this.titleEl&&(d?(this.titleEl.textContent=d,this.titleEl.hidden=!1):this.titleEl.hidden=!0),this.descEl&&(c?(this.descEl.textContent=c,this.descEl.hidden=!1):this.descEl.hidden=!0),this.overlayEl&&(this.overlayEl.hidden=!o),this.dimOverlayEl&&(this.dimOverlayEl.hidden=!o),this.progressEl&&(this.progressEl.hidden=!r),this.timeEl&&(this.timeEl.hidden=!a),this.volumeWrapEl&&(this.volumeWrapEl.hidden=!h),this.fullscreenButtonEl&&(this.fullscreenButtonEl.hidden=!u),this.fullscreenButtonEl&&this.fullscreenButtonEl.replaceChildren(t(this.isFullscreen?"shrink":"expand",i)),this.updatePlayIcons(),this.updateProgress(),this.updateTime(),this.updateVolumeUI()}}customElements.get("db-video-player")||customElements.define("db-video-player",DbVideoPlayer);export{DbVideoPlayer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function applySectionBlockHeader(e,t){if(!e)return;const{headline:n="",subtitle:i="",description:s="",badge:a,align:d="center",extraClassName:o}=t;e.className=["designbase-wc-section-block-header",`designbase-wc-section-block-header--${d}`,o].filter(Boolean).join(" ");let r=e.querySelector("db-badge");a?.text?(r||(r=document.createElement("db-badge"),r.className="designbase-wc-section-block-header__badge",e.prepend(r)),r.removeAttribute("hidden"),r.style.display="",r.setAttribute("variant",a.variant||"primary"),r.setAttribute("
|
|
1
|
+
function applySectionBlockHeader(e,t){if(!e)return;const{headline:n="",subtitle:i="",description:s="",badge:a,align:d="center",extraClassName:o}=t;e.className=["designbase-wc-section-block-header",`designbase-wc-section-block-header--${d}`,o].filter(Boolean).join(" ");let r=e.querySelector("db-badge");a?.text?(r||(r=document.createElement("db-badge"),r.className="designbase-wc-section-block-header__badge",e.prepend(r)),r.removeAttribute("hidden"),r.style.display="",r.setAttribute("variant",a.variant||"primary"),r.setAttribute("type",a.style||"text"),r.setAttribute("size",a.size||"m"),r.textContent=a.text):r&&(r.textContent="",r.setAttribute("hidden",""),r.style.display="none");const c=e.querySelector("h2"),l=e.querySelector(".designbase-wc-section-block-header__subtitle"),b=e.querySelector(".designbase-wc-section-block-header__description");c&&(c.textContent=n,c.hidden=!n),l&&(l.textContent=i,l.hidden=!i),b&&(b.textContent=s,b.hidden=!s)}export{applySectionBlockHeader};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 접근성 런타임 유틸.
|
|
3
|
+
*
|
|
4
|
+
* 규격: A11Y-LIVE, A11Y-NAME
|
|
5
|
+
*/
|
|
6
|
+
type Politeness = 'polite' | 'assertive';
|
|
7
|
+
/**
|
|
8
|
+
* 스크린리더에 메시지를 알린다.
|
|
9
|
+
*
|
|
10
|
+
* @param message 읽어줄 문장
|
|
11
|
+
* @param politeness `polite` 는 현재 읽는 것을 끝낸 뒤, `assertive` 는 즉시 끼어든다.
|
|
12
|
+
* 오류·경고에만 assertive 를 쓴다.
|
|
13
|
+
*/
|
|
14
|
+
declare function announce(message: string, politeness?: Politeness): void;
|
|
15
|
+
/** 피드백 톤에 맞는 politeness. danger/warning 만 즉시 끼어든다. */
|
|
16
|
+
declare function politenessForTone(tone: string): Politeness;
|
|
17
|
+
/**
|
|
18
|
+
* 접근 가능한 이름을 붙인다.
|
|
19
|
+
*
|
|
20
|
+
* 보이는 텍스트가 이미 있으면 `aria-label` 로 덮어쓰지 않는다 — 스크린리더 사용자와
|
|
21
|
+
* 음성 제어 사용자가 서로 다른 이름을 보게 되는 문제를 막는다.
|
|
22
|
+
*/
|
|
23
|
+
declare function setAccessibleName(el: HTMLElement, name: string): void;
|
|
24
|
+
/** 시각적으로만 감춘 텍스트 노드를 만든다 (스크린리더는 읽음) */
|
|
25
|
+
declare function createSrOnly(text: string): HTMLSpanElement;
|
|
26
|
+
/**
|
|
27
|
+
* 네이티브가 아닌 요소(`role="button"` 을 얹은 div 등)에 Enter/Space 활성화를 붙인다.
|
|
28
|
+
*
|
|
29
|
+
* 네이티브 `<button>` 은 브라우저가 이걸 해 주지만, 커스텀 엘리먼트 호스트에
|
|
30
|
+
* role 만 얹은 경우에는 직접 배선해야 마우스와 키보드가 같아진다.
|
|
31
|
+
*
|
|
32
|
+
* 규격: A11Y-KEYBOARD
|
|
33
|
+
*
|
|
34
|
+
* @returns 등록을 해제하는 함수
|
|
35
|
+
*/
|
|
36
|
+
declare function activateOnKey(el: HTMLElement, options?: {
|
|
37
|
+
isDisabled?: () => boolean;
|
|
38
|
+
}): () => void;
|
|
39
|
+
|
|
40
|
+
export { activateOnKey, announce, createSrOnly, politenessForTone, setAccessibleName };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={};function announce(t,n="polite"){if("undefined"==typeof document||!t)return;const r=function getRegion(t){const n=e[t];if(n&&n.isConnected)return n;const r=document.createElement("div");return r.setAttribute("aria-live",t),r.setAttribute("aria-atomic","true"),r.setAttribute("role","assertive"===t?"alert":"status"),r.setAttribute("data-wc-internal","live-region"),r.className="designbase-wc-sr-only",document.body.appendChild(r),e[t]=r,r}(n);r.textContent="",requestAnimationFrame(()=>{r.textContent=t})}function politenessForTone(e){return"danger"===e||"warning"===e?"assertive":"polite"}function setAccessibleName(e,t){if(!t)return;(e.textContent||"").trim().length>0||e.setAttribute("aria-label",t)}function createSrOnly(e){const t=document.createElement("span");return t.className="designbase-wc-sr-only",t.textContent=e,t}function activateOnKey(e,t={}){const handler=n=>{"Enter"!==n.key&&" "!==n.key||t.isDisabled?.()||(n.preventDefault(),e.click())};return e.addEventListener("keydown",handler),()=>e.removeEventListener("keydown",handler)}export{activateOnKey,announce,createSrOnly,politenessForTone,setAccessibleName};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CleanupBag } from './dom.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 선택적 베이스 클래스.
|
|
5
|
+
*
|
|
6
|
+
* 기존 130개 컴포넌트는 전부 `extends HTMLElement` 이며 그대로 동작한다.
|
|
7
|
+
* 새 컴포넌트나 개선 대상만 이 클래스를 상속하면 정리(cleanup)와 이벤트 발행이
|
|
8
|
+
* 규격에 맞게 강제된다. **상속은 강제가 아니다.**
|
|
9
|
+
*
|
|
10
|
+
* 규격: PERF-CLEANUP, STD-EVENT
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
declare abstract class DbElement extends HTMLElement {
|
|
14
|
+
/**
|
|
15
|
+
* `connectedCallback` 에서 등록한 것들을 담아두는 가방.
|
|
16
|
+
* `disconnectedCallback` 에서 자동으로 비워진다.
|
|
17
|
+
*/
|
|
18
|
+
protected readonly cleanups: CleanupBag;
|
|
19
|
+
/**
|
|
20
|
+
* 하위 클래스는 `disconnectedCallback` 을 오버라이드할 때 반드시
|
|
21
|
+
* `super.disconnectedCallback()` 을 호출해야 한다.
|
|
22
|
+
*/
|
|
23
|
+
disconnectedCallback(): void;
|
|
24
|
+
/**
|
|
25
|
+
* 규격에 맞는 커스텀 이벤트를 발행한다 — `db-` 접두사 + bubbles + composed.
|
|
26
|
+
*
|
|
27
|
+
* @returns `preventDefault()` 되지 않았으면 true
|
|
28
|
+
*/
|
|
29
|
+
protected dbEmit<T>(name: string, detail?: T, options?: {
|
|
30
|
+
cancelable?: boolean;
|
|
31
|
+
}): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* 커스텀 엘리먼트가 정의되기 **전에** 프레임워크가 프로퍼티로 값을 넣어둔 경우,
|
|
34
|
+
* 그 값은 인스턴스 자체 프로퍼티로 남아 접근자를 가린다. 정의 후 회수한다.
|
|
35
|
+
*
|
|
36
|
+
* 규격: STD-UPGRADE
|
|
37
|
+
*/
|
|
38
|
+
protected upgradeProperties(names: string[]): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { DbElement };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CleanupBag as e}from"./dom.js";class DbElement extends HTMLElement{constructor(){super(...arguments),this.cleanups=new e}disconnectedCallback(){this.cleanups.run()}dbEmit(e,t,s){const n=e.startsWith("db-")?e:`db-${e}`;return this.dispatchEvent(new CustomEvent(n,{detail:t,bubbles:!0,composed:!0,cancelable:s?.cancelable??!1}))}upgradeProperties(e){for(const t of e){if(!Object.prototype.hasOwnProperty.call(this,t))continue;const e=this[t];delete this[t],this[t]=e}}}export{DbElement};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 정리(cleanup) 관리.
|
|
3
|
+
*
|
|
4
|
+
* `connectedCallback` 에서 등록한 리스너·타이머·observer 를 `disconnectedCallback`
|
|
5
|
+
* 에서 빠짐없이 해제하기 위한 도구. 등록과 해제를 한 줄로 묶어두면
|
|
6
|
+
* "add 는 했는데 remove 를 빠뜨리는" 실수가 구조적으로 불가능해진다.
|
|
7
|
+
*
|
|
8
|
+
* 규격: PERF-CLEANUP
|
|
9
|
+
*/
|
|
10
|
+
type Cleanup = () => void;
|
|
11
|
+
/** 해제 함수를 모아뒀다가 한 번에 실행하는 가방 */
|
|
12
|
+
declare class CleanupBag {
|
|
13
|
+
private items;
|
|
14
|
+
/** 해제 함수를 직접 등록 */
|
|
15
|
+
add(fn: Cleanup): void;
|
|
16
|
+
/** 리스너를 달고 해제 함수를 자동 등록 */
|
|
17
|
+
listen<T extends EventTarget>(target: T, type: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
18
|
+
/** setTimeout — 해제 시 자동 clear */
|
|
19
|
+
timeout(fn: () => void, ms: number): number;
|
|
20
|
+
/** setInterval — 해제 시 자동 clear */
|
|
21
|
+
interval(fn: () => void, ms: number): number;
|
|
22
|
+
/** MutationObserver / ResizeObserver / IntersectionObserver 등록 */
|
|
23
|
+
observe<T extends {
|
|
24
|
+
disconnect(): void;
|
|
25
|
+
}>(observer: T): T;
|
|
26
|
+
/** 등록된 모든 해제 함수를 실행하고 비운다. 여러 번 호출해도 안전하다. */
|
|
27
|
+
run(): void;
|
|
28
|
+
}
|
|
29
|
+
/** 스크롤/리사이즈처럼 고빈도로 발생하는 핸들러를 프레임당 1회로 병합한다. (PERF-RAF) */
|
|
30
|
+
declare function rafThrottle<A extends unknown[]>(fn: (...args: A) => void): ((...args: A) => void) & {
|
|
31
|
+
cancel(): void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { CleanupBag, rafThrottle };
|
|
35
|
+
export type { Cleanup };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class CleanupBag{constructor(){this.items=[]}add(t){this.items.push(t)}listen(t,e,s,n){t.addEventListener(e,s,n),this.items.push(()=>t.removeEventListener(e,s,n))}timeout(t,e){const s=window.setTimeout(t,e);return this.items.push(()=>window.clearTimeout(s)),s}interval(t,e){const s=window.setInterval(t,e);return this.items.push(()=>window.clearInterval(s)),s}observe(t){return this.items.push(()=>t.disconnect()),t}run(){const t=this.items;this.items=[];for(const e of t)try{e()}catch{}}}function rafThrottle(t){let e,s=0;const wrapped=(...n)=>{e=n,s||(s=requestAnimationFrame(()=>{s=0,t(...e)}))};return wrapped.cancel=()=>{s&&cancelAnimationFrame(s),s=0},wrapped}export{CleanupBag,rafThrottle};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 포커스 트랩.
|
|
3
|
+
*
|
|
4
|
+
* 모달·드로어·바텀시트 같은 오버레이가 열려 있는 동안 Tab 이 오버레이 밖으로
|
|
5
|
+
* 새어나가지 않게 하고, 닫을 때 **열기 직전에 포커스를 갖고 있던 요소로 되돌린다**.
|
|
6
|
+
* 포커스 복원이 없으면 키보드 사용자는 모달을 닫는 순간 문서 맨 앞으로 튕긴다.
|
|
7
|
+
*
|
|
8
|
+
* 규격: A11Y-TRAP, A11Y-AUTOFOCUS
|
|
9
|
+
*/
|
|
10
|
+
/** 컨테이너 안에서 Tab 순서대로 포커스 가능한 요소들 */
|
|
11
|
+
declare function getTabbables(container: HTMLElement): HTMLElement[];
|
|
12
|
+
interface FocusTrapOptions {
|
|
13
|
+
/**
|
|
14
|
+
* 활성화 시 포커스를 받을 요소.
|
|
15
|
+
* 지정하지 않으면 `[autofocus]` → 첫 포커서블 → 컨테이너 순으로 결정한다.
|
|
16
|
+
*/
|
|
17
|
+
initialFocus?: HTMLElement | (() => HTMLElement | null) | null;
|
|
18
|
+
/** 해제 시 포커스를 돌려줄 요소. 기본값은 활성화 직전의 `document.activeElement` */
|
|
19
|
+
returnFocus?: HTMLElement | null;
|
|
20
|
+
/** 포커스 복원을 끄고 싶을 때 (기본 true) */
|
|
21
|
+
restoreFocus?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface FocusTrap {
|
|
24
|
+
activate(): void;
|
|
25
|
+
deactivate(): void;
|
|
26
|
+
/** 컨테이너 내용이 바뀌었을 때 포커서블 목록을 다시 계산할 필요는 없다 — 매번 조회하므로 no-op 호환용 */
|
|
27
|
+
readonly active: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `container` 안에 포커스를 가두는 트랩을 만든다.
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* this.trap = createFocusTrap(panel);
|
|
34
|
+
* this.trap.activate(); // 열 때
|
|
35
|
+
* this.trap.deactivate(); // 닫을 때 — 포커스가 트리거로 돌아간다
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function createFocusTrap(container: HTMLElement, options?: FocusTrapOptions): FocusTrap;
|
|
39
|
+
|
|
40
|
+
export { createFocusTrap, getTabbables };
|
|
41
|
+
export type { FocusTrap, FocusTrapOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=["a[href]","area[href]","button","input","select","textarea","details > summary:first-of-type","iframe","object","embed","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"[tabindex]"].join(",");function isTabbable(e){return e instanceof HTMLElement&&(!e.hasAttribute("disabled")&&("true"!==e.getAttribute("aria-hidden")&&(!(e.tabIndex<0)&&(("function"!=typeof e.closest||!e.closest("[inert]"))&&(!e.hidden&&(!(!e.offsetParent&&"fixed"!==getComputedStyle(e).position)&&"hidden"!==getComputedStyle(e).visibility))))))}function getTabbables(t){return Array.from(t.querySelectorAll(e)).filter(isTabbable)}function createFocusTrap(e,t={}){let n=!1,o=null;const handleKeyDown=t=>{if("Tab"!==t.key||!n)return;const o=getTabbables(e);if(0===o.length)return t.preventDefault(),void e.focus();const r=o[0],i=o[o.length-1],a=document.activeElement;if(!a||!e.contains(a))return t.preventDefault(),void(t.shiftKey?i:r).focus();t.shiftKey&&a===r?(t.preventDefault(),i.focus()):t.shiftKey||a!==i||(t.preventDefault(),r.focus())},handleFocusIn=t=>{if(!n)return;const o=t.target;if(o&&e.contains(o))return;(getTabbables(e)[0]||e).focus()};return{get active(){return n},activate(){if(n)return;n=!0,o=t.returnFocus??document.activeElement,e.hasAttribute("tabindex")||e.setAttribute("tabindex","-1"),document.addEventListener("keydown",handleKeyDown,!0),document.addEventListener("focusin",handleFocusIn,!0);((()=>{const{initialFocus:n}=t;if("function"==typeof n)return n();if(n)return n;const o=e.querySelector("[autofocus]");return o&&isTabbable(o)?o:getTabbables(e)[0]||null})()||e).focus()},deactivate(){n&&(n=!1,document.removeEventListener("keydown",handleKeyDown,!0),document.removeEventListener("focusin",handleFocusIn,!0),!1!==t.restoreFocus&&(o&&o.isConnected&&"function"==typeof o.focus&&o.focus(),o=null))}}}export{createFocusTrap,getTabbables};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 사용자 노출 문자열.
|
|
3
|
+
*
|
|
4
|
+
* 컴포넌트 소스에 `'모달 닫기'` 같은 한국어를 박아두면 한국어 외 서비스에서는
|
|
5
|
+
* 스크린리더가 엉뚱한 언어를 읽는다. 문자열은 전부 여기를 통과시키고,
|
|
6
|
+
* 소비자가 `dbSetLocale()` 로 덮어쓸 수 있게 한다.
|
|
7
|
+
*
|
|
8
|
+
* 기본값은 현재 소스에 박혀 있던 한국어 그대로다 — 동작 변화 없이 교체 지점만 생긴다.
|
|
9
|
+
*
|
|
10
|
+
* 규격: DX-I18N
|
|
11
|
+
*/
|
|
12
|
+
/** 컴포넌트가 쓰는 문자열 키 → 기본(한국어) 값 */
|
|
13
|
+
declare const DEFAULT_STRINGS: {
|
|
14
|
+
readonly close: "닫기";
|
|
15
|
+
readonly 'modal.close': "모달 닫기";
|
|
16
|
+
readonly 'drawer.close': "드로어 닫기";
|
|
17
|
+
readonly 'dialog.close': "대화상자 닫기";
|
|
18
|
+
readonly 'toast.close': "알림 닫기";
|
|
19
|
+
readonly clear: "지우기";
|
|
20
|
+
readonly clearAll: "모든 값 지우기";
|
|
21
|
+
readonly loading: "로딩 중";
|
|
22
|
+
readonly required: "필수";
|
|
23
|
+
readonly optional: "선택";
|
|
24
|
+
readonly error: "오류";
|
|
25
|
+
readonly success: "성공";
|
|
26
|
+
readonly warning: "경고";
|
|
27
|
+
readonly info: "안내";
|
|
28
|
+
readonly previous: "이전";
|
|
29
|
+
readonly next: "다음";
|
|
30
|
+
readonly 'select.placeholder': "선택하세요";
|
|
31
|
+
readonly 'select.noResults': "결과 없음";
|
|
32
|
+
readonly 'tabs.label': "탭 목록";
|
|
33
|
+
readonly 'pagination.label': "페이지 번호";
|
|
34
|
+
readonly 'search.label': "검색";
|
|
35
|
+
readonly 'media.play': "재생";
|
|
36
|
+
readonly 'media.pause': "일시정지";
|
|
37
|
+
readonly 'media.mute': "음소거";
|
|
38
|
+
readonly 'media.unmute': "음소거 해제";
|
|
39
|
+
readonly 'media.settings': "설정";
|
|
40
|
+
readonly 'media.shuffle': "셔플";
|
|
41
|
+
readonly 'media.repeat': "반복";
|
|
42
|
+
readonly 'media.previous': "이전 트랙";
|
|
43
|
+
readonly 'media.next': "다음 트랙";
|
|
44
|
+
readonly 'media.fullscreen': "전체화면";
|
|
45
|
+
readonly 'carousel.previous': "이전 슬라이드";
|
|
46
|
+
readonly 'carousel.next': "다음 슬라이드";
|
|
47
|
+
readonly 'breadcrumbs.expand': "생략된 경로 펼치기";
|
|
48
|
+
readonly 'input.showPassword': "비밀번호 보기";
|
|
49
|
+
readonly 'input.hidePassword': "비밀번호 숨기기";
|
|
50
|
+
readonly 'input.clearSearch': "검색어 지우기";
|
|
51
|
+
readonly 'verify.done': "인증 완료";
|
|
52
|
+
readonly 'verify.phoneDone': "휴대폰 번호 인증이 완료되었습니다.";
|
|
53
|
+
};
|
|
54
|
+
type DbStringKey = keyof typeof DEFAULT_STRINGS;
|
|
55
|
+
type DbStrings = Partial<Record<DbStringKey, string>> & Record<string, string>;
|
|
56
|
+
/**
|
|
57
|
+
* 문자열을 전역으로 덮어쓴다. 앱 부트스트랩에서 한 번 호출한다.
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* dbSetLocale({ 'modal.close': 'Close dialog', close: 'Close' });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function dbSetLocale(strings: DbStrings): void;
|
|
64
|
+
/** 현재 적용된 오버라이드를 모두 지운다 (테스트용) */
|
|
65
|
+
declare function dbResetLocale(): void;
|
|
66
|
+
/**
|
|
67
|
+
* 문자열을 조회한다.
|
|
68
|
+
*
|
|
69
|
+
* @param key 문자열 키
|
|
70
|
+
* @param fallback 키가 사전에 없을 때 쓸 값
|
|
71
|
+
*/
|
|
72
|
+
declare function t(key: DbStringKey | string, fallback?: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* 엘리먼트에서 라벨을 읽는다 — 소비자가 준 속성이 항상 우선한다.
|
|
75
|
+
*
|
|
76
|
+
* 컴포넌트는 `label-close` 같은 속성으로 개별 오버라이드를 받고,
|
|
77
|
+
* 없으면 전역 로케일로 폴백한다.
|
|
78
|
+
*/
|
|
79
|
+
declare function labelFrom(el: Element, attribute: string, key: DbStringKey | string): string;
|
|
80
|
+
|
|
81
|
+
export { dbResetLocale, dbSetLocale, labelFrom, t };
|
|
82
|
+
export type { DbStringKey, DbStrings };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={close:"닫기","modal.close":"모달 닫기","drawer.close":"드로어 닫기","dialog.close":"대화상자 닫기","toast.close":"알림 닫기",clear:"지우기",clearAll:"모든 값 지우기",loading:"로딩 중",required:"필수",optional:"선택",error:"오류",success:"성공",warning:"경고",info:"안내",previous:"이전",next:"다음","select.placeholder":"선택하세요","select.noResults":"결과 없음","tabs.label":"탭 목록","pagination.label":"페이지 번호","search.label":"검색","media.play":"재생","media.pause":"일시정지","media.mute":"음소거","media.unmute":"음소거 해제","media.settings":"설정","media.shuffle":"셔플","media.repeat":"반복","media.previous":"이전 트랙","media.next":"다음 트랙","media.fullscreen":"전체화면","carousel.previous":"이전 슬라이드","carousel.next":"다음 슬라이드","breadcrumbs.expand":"생략된 경로 펼치기","input.showPassword":"비밀번호 보기","input.hidePassword":"비밀번호 숨기기","input.clearSearch":"검색어 지우기","verify.done":"인증 완료","verify.phoneDone":"휴대폰 번호 인증이 완료되었습니다."};let a={};function dbSetLocale(e){a={...a,...e}}function dbResetLocale(){a={}}function t(l,o){return l in a?a[l]:l in e?e[l]:o??l}function labelFrom(e,a,l){return e.getAttribute(a)||t(l)}export{dbResetLocale,dbSetLocale,labelFrom,t};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 문서 스크롤 잠금.
|
|
3
|
+
*
|
|
4
|
+
* 각 오버레이가 `document.body.style.overflow` 를 직접 만지면, 모달 위에 드로어를
|
|
5
|
+
* 띄웠다가 드로어만 닫는 순간 스크롤락이 통째로 풀린다. 잠금은 참조 카운트로만 관리한다.
|
|
6
|
+
*
|
|
7
|
+
* 규격: PERF-GLOBAL-STATE
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* 문서 스크롤을 잠근다. 중첩 호출은 카운트되며, 같은 횟수만큼 `unlockScroll()`
|
|
11
|
+
* 해야 실제로 풀린다.
|
|
12
|
+
*/
|
|
13
|
+
declare function lockScroll(): void;
|
|
14
|
+
/** `lockScroll()` 을 되돌린다. 카운트가 0 이 될 때만 실제로 해제된다. */
|
|
15
|
+
declare function unlockScroll(): void;
|
|
16
|
+
/** 현재 잠금 카운트 — 테스트/디버깅용 */
|
|
17
|
+
declare function scrollLockDepth(): number;
|
|
18
|
+
|
|
19
|
+
export { lockScroll, scrollLockDepth, unlockScroll };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let t=0,e="",n="";function lockScroll(){if("undefined"==typeof document)return;if(t+=1,t>1)return;const{body:o}=document;e=o.style.overflow,n=o.style.paddingRight;const l=function scrollbarWidth(){return window.innerWidth-document.documentElement.clientWidth}();if(l>0){const t=parseFloat(getComputedStyle(o).paddingRight)||0;o.style.paddingRight=`${t+l}px`}o.style.overflow="hidden"}function unlockScroll(){if("undefined"==typeof document)return;if(0===t)return;if(t-=1,t>0)return;const{body:o}=document;o.style.overflow=e,o.style.paddingRight=n,e="",n=""}function scrollLockDepth(){return t}export{lockScroll,scrollLockDepth,unlockScroll};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 엘리먼트 id 생성.
|
|
3
|
+
*
|
|
4
|
+
* 각 컴포넌트가 `Math.random().toString(36)` 을 따로 굴리면 충돌 가능성이 있고,
|
|
5
|
+
* 무엇보다 SSR/스냅샷에서 diff 노이즈가 된다. 생성은 여기서만 한다.
|
|
6
|
+
*
|
|
7
|
+
* 규격: DX-UID
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* `prefix` 로 시작하는 문서 내 고유 id 를 만든다.
|
|
11
|
+
*
|
|
12
|
+
* `crypto.randomUUID` 가 있으면 그 앞 8자리를 쓰고, 없으면 증가 카운터로 폴백한다.
|
|
13
|
+
* 어느 쪽이든 한 문서 안에서는 절대 겹치지 않는다.
|
|
14
|
+
*/
|
|
15
|
+
declare function dbUid(prefix?: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* 엘리먼트에 id 가 없으면 만들어 붙이고, 그 id 를 돌려준다.
|
|
18
|
+
*
|
|
19
|
+
* `aria-labelledby` / `aria-controls` 배선에 쓴다 — 소비자가 직접 지정한 id 는 존중한다.
|
|
20
|
+
*/
|
|
21
|
+
declare function ensureId(el: Element, prefix?: string): string;
|
|
22
|
+
|
|
23
|
+
export { dbUid, ensureId };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let d=0;function dbUid(n="db"){d+=1;const o="undefined"!=typeof globalThis?globalThis.crypto:void 0;return`${n}-${o&&"function"==typeof o.randomUUID?o.randomUUID().slice(0,8):Math.floor(4294967295*Math.random()).toString(36)}-${d}`}function ensureId(d,n="db"){return d.id||(d.id=dbUid(n)),d.id}export{dbUid,ensureId};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { DbToast } from '../components/db-toast.js';
|
|
2
|
+
import { ToastVariant, ToastPosition } from '@designbasekorea/ui-contracts';
|
|
3
|
+
export { ToastPosition as DbToastPosition, ToastVariant as DbToastVariant } from '@designbasekorea/ui-contracts';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* 명령형 toast() 헬퍼 — 컨테이너/스택/제거를 자동 관리합니다.
|
|
@@ -6,24 +8,22 @@ import { DbToast } from '../components/db-toast.js';
|
|
|
6
8
|
* @example
|
|
7
9
|
* import { toast } from '@designbasekorea/ui-wc';
|
|
8
10
|
* toast.success('저장되었습니다');
|
|
9
|
-
* toast.
|
|
10
|
-
* const t = toast({ title: '업로드 중…',
|
|
11
|
+
* toast.danger('오류가 발생했어요', { description: '잠시 후 다시 시도해 주세요' });
|
|
12
|
+
* const t = toast({ title: '업로드 중…', variant: 'info', duration: 0 });
|
|
11
13
|
* t.close();
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
|
-
type DbToastStatus = 'info' | 'success' | 'warning' | 'error';
|
|
15
|
-
type DbToastPosition = 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left';
|
|
16
16
|
interface DbToastOptions {
|
|
17
17
|
/** 제목 (필수) */
|
|
18
18
|
title: string;
|
|
19
19
|
/** 보조 설명 */
|
|
20
20
|
description?: string;
|
|
21
21
|
/** 상태 색상/아이콘. 기본 'info' */
|
|
22
|
-
|
|
22
|
+
variant?: ToastVariant;
|
|
23
23
|
/** 자동 닫힘 시간(ms). 0 이하이면 자동으로 닫히지 않음. 기본 5000 */
|
|
24
24
|
duration?: number;
|
|
25
25
|
/** 표시 위치. 기본 'top-right' */
|
|
26
|
-
position?:
|
|
26
|
+
position?: ToastPosition;
|
|
27
27
|
/** 닫기 버튼 표시. 기본 true */
|
|
28
28
|
showClose?: boolean;
|
|
29
29
|
/** 진행 바 표시. 기본 false */
|
|
@@ -37,20 +37,20 @@ interface DbToastHandle {
|
|
|
37
37
|
/** 토스트를 수동으로 닫습니다(애니메이션 포함). */
|
|
38
38
|
close: () => void;
|
|
39
39
|
}
|
|
40
|
-
type DbToastShortcut = (title: string, options?: Omit<DbToastOptions, 'title' | '
|
|
40
|
+
type DbToastShortcut = (title: string, options?: Omit<DbToastOptions, 'title' | 'variant'>) => DbToastHandle;
|
|
41
41
|
interface DbToastApi {
|
|
42
42
|
(options: DbToastOptions): DbToastHandle;
|
|
43
43
|
show: (options: DbToastOptions) => DbToastHandle;
|
|
44
44
|
success: DbToastShortcut;
|
|
45
|
-
|
|
45
|
+
danger: DbToastShortcut;
|
|
46
46
|
warning: DbToastShortcut;
|
|
47
47
|
info: DbToastShortcut;
|
|
48
48
|
/** 열려 있는 토스트를 모두 닫습니다. position 지정 시 해당 위치만. */
|
|
49
|
-
dismissAll: (position?:
|
|
49
|
+
dismissAll: (position?: ToastPosition) => void;
|
|
50
50
|
}
|
|
51
51
|
/** 토스트를 띄웁니다. `toast(options)` 와 동일합니다. */
|
|
52
52
|
declare function showToast(options: DbToastOptions): DbToastHandle;
|
|
53
53
|
declare const toast: DbToastApi;
|
|
54
54
|
|
|
55
55
|
export { showToast, toast };
|
|
56
|
-
export type { DbToastApi, DbToastHandle, DbToastOptions
|
|
56
|
+
export type { DbToastApi, DbToastHandle, DbToastOptions };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const t="designbase-wc-toast-container",e="top-right";let o=0;function isBrowser(){return"undefined"!=typeof document}function showToast(s){const
|
|
1
|
+
const t="designbase-wc-toast-container",e="top-right";let o=0;function isBrowser(){return"undefined"!=typeof document}function showToast(s){const n=`db-toast-${o+=1}`;if(!isBrowser())return{id:n,element:null,close:()=>{}};const{title:r,description:i,variant:a="info",duration:u=5e3,position:c=e,showClose:d=!0,showProgress:l=!1}=s,h=function getContainer(e){const o=document.querySelector(`.${t}[data-position="${e}"]`);if(o)return o;const s=document.createElement("div");return s.className=`${t} ${t}--${e}`,s.dataset.position=e,s.setAttribute("role","region"),s.setAttribute("aria-live","polite"),s.setAttribute("aria-label","알림"),document.body.appendChild(s),s}(c),b=document.createElement("db-toast");return b.setAttribute("toast-id",n),b.setAttribute("variant",a),b.setAttribute("title",r),i&&b.setAttribute("description",i),b.setAttribute("duration",String(u)),b.setAttribute("show-close",String(d)),l&&b.setAttribute("show-progress",""),b.setAttribute("auto-remove","true"),c.startsWith("bottom")?h.insertBefore(b,h.firstChild):h.appendChild(b),b.addEventListener("db-close",()=>window.requestAnimationFrame(()=>function cleanupContainer(t){0===t.childElementCount&&t.remove()}(h)),{once:!0}),{id:n,element:b,close:()=>b.close()}}function shortcut(t){return(e,o)=>showToast({...o,title:e,variant:t})}const toast=t=>showToast(t);toast.show=showToast,toast.success=shortcut("success"),toast.danger=shortcut("danger"),toast.warning=shortcut("warning"),toast.info=shortcut("info"),toast.dismissAll=e=>{if(!isBrowser())return;const o=e?`.${t}[data-position="${e}"] db-toast`:`.${t} db-toast`;document.querySelectorAll(o).forEach(t=>t.close())};export{showToast,toast};
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export { dbUid, ensureId } from './core/uid.js';
|
|
2
|
+
export { Cleanup, CleanupBag, rafThrottle } from './core/dom.js';
|
|
3
|
+
export { FocusTrap, FocusTrapOptions, createFocusTrap, getTabbables } from './core/focus-trap.js';
|
|
4
|
+
export { lockScroll, scrollLockDepth, unlockScroll } from './core/scroll-lock.js';
|
|
5
|
+
export { activateOnKey, announce, createSrOnly, politenessForTone, setAccessibleName } from './core/a11y.js';
|
|
6
|
+
export { DbStringKey, DbStrings, dbResetLocale, dbSetLocale, labelFrom, t } from './core/i18n.js';
|
|
7
|
+
export { DbElement } from './core/base-element.js';
|
|
1
8
|
export { DbButton } from './components/db-button.js';
|
|
2
9
|
export { DbInput } from './components/db-input.js';
|
|
3
10
|
export { DbCheckbox } from './components/db-checkbox.js';
|
|
@@ -39,6 +46,7 @@ export { DbShare, DbSharePlatform, DbShareVariant } from './components/db-share.
|
|
|
39
46
|
export { DbTopBanner } from './components/db-top-banner.js';
|
|
40
47
|
export { DbCookieBanner } from './components/db-cookie-banner.js';
|
|
41
48
|
export { DbStat, DbStatChange } from './components/db-stat.js';
|
|
49
|
+
export { DbActivityHeatmap, DbActivityHeatmapCellDetail, DbActivityHeatmapItem } from './components/db-activity-heatmap.js';
|
|
42
50
|
export { DbImage } from './components/db-image.js';
|
|
43
51
|
export { DbImagePlaceholder } from './components/db-image-placeholder.js';
|
|
44
52
|
export { DbImageList, DbImageListItem } from './components/db-image-list.js';
|
|
@@ -109,7 +117,6 @@ export { DbTutorial } from './components/db-tutorial.js';
|
|
|
109
117
|
export { DbAnimationBackground } from './components/db-animation-background.js';
|
|
110
118
|
export { DbGridBackground } from './components/db-grid-background.js';
|
|
111
119
|
export { DbCursorFollower } from './components/db-cursor-follower.js';
|
|
112
|
-
export { DbLottie } from './components/db-lottie.js';
|
|
113
120
|
export { DbDownloadProgress } from './components/db-download-progress.js';
|
|
114
121
|
export { DbOtpInput } from './components/db-otp-input.js';
|
|
115
122
|
export { DbPhoneVerify } from './components/db-phone-verify.js';
|
|
@@ -119,12 +126,13 @@ export { DbChatOrb } from './components/db-chat-orb.js';
|
|
|
119
126
|
export { DbChatWelcome } from './components/db-chat-welcome.js';
|
|
120
127
|
export { DbChatAttachOption, DbChatComposer, DbChatTool } from './components/db-chat-composer.js';
|
|
121
128
|
export { DbChatSuggestion, DbChatSuggestions } from './components/db-chat-suggestions.js';
|
|
122
|
-
export { DbChatMessage } from './components/db-chat-message.js';
|
|
129
|
+
export { DbChatMessage, DbChatMessageAttachment } from './components/db-chat-message.js';
|
|
123
130
|
export { DbChatTyping } from './components/db-chat-typing.js';
|
|
124
|
-
export { DbChatWidget, DbChatWidgetConversation, DbChatWidgetMessage } from './components/db-chat-widget.js';
|
|
131
|
+
export { DbChatWidget, DbChatWidgetArticle, DbChatWidgetConversation, DbChatWidgetMenuAction, DbChatWidgetMenuItem, DbChatWidgetMessage } from './components/db-chat-widget.js';
|
|
125
132
|
export { DbAiChat, DbArtifactOptions, DbChatArtifactRef, DbChatMessageData } from './components/db-ai-chat.js';
|
|
126
133
|
export { DbAiChatItem, DbAiChatList } from './components/db-ai-chat-list.js';
|
|
127
134
|
export { DbChatList, DbChatListItem } from './components/db-chat-list.js';
|
|
128
135
|
export { DbChatRoom, DbChatRoomMessage } from './components/db-chat-room.js';
|
|
129
|
-
export { DbToastApi, DbToastHandle, DbToastOptions,
|
|
136
|
+
export { DbToastApi, DbToastHandle, DbToastOptions, toast as dbToast, showToast, toast } from './helpers/toast.js';
|
|
130
137
|
export { DbConfirmOptions, DbConfirmVariant, confirm, confirm as dbConfirm } from './helpers/confirm.js';
|
|
138
|
+
export { ToastPosition as DbToastPosition, ToastVariant as DbToastVariant } from '@designbasekorea/ui-contracts';
|