@designbasekorea/ui-wc 0.9.4 → 0.10.0
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 +8 -0
- package/README.md +2 -0
- package/dist/esm/components/db-accordion.d.ts +22 -10
- package/dist/esm/components/db-accordion.js +1 -1
- package/dist/esm/components/db-avatar.js +1 -1
- package/dist/esm/components/db-button.d.ts +33 -1
- package/dist/esm/components/db-button.js +1 -1
- package/dist/esm/components/db-chat-orb.d.ts +13 -0
- package/dist/esm/components/db-chat-orb.js +1 -1
- package/dist/esm/components/db-chat-room.js +1 -1
- package/dist/esm/components/db-chat-widget.js +1 -1
- package/dist/esm/components/db-checkbox.d.ts +3 -0
- package/dist/esm/components/db-checkbox.js +1 -1
- package/dist/esm/components/db-confetti.d.ts +11 -5
- package/dist/esm/components/db-confetti.js +1 -1
- package/dist/esm/components/db-email-verify.d.ts +36 -7
- package/dist/esm/components/db-email-verify.js +1 -1
- package/dist/esm/components/db-floating-action-button.d.ts +1 -1
- package/dist/esm/components/db-floating-action-button.js +1 -1
- package/dist/esm/components/db-input.d.ts +8 -1
- package/dist/esm/components/db-input.js +1 -1
- package/dist/esm/components/db-navbar.d.ts +3 -0
- package/dist/esm/components/db-navbar.js +1 -1
- package/dist/esm/components/db-otp-input.js +1 -1
- package/dist/esm/components/db-page-header.d.ts +11 -0
- package/dist/esm/components/db-page-header.js +1 -1
- package/dist/esm/components/db-phone-verify.d.ts +33 -11
- package/dist/esm/components/db-phone-verify.js +1 -1
- package/dist/esm/components/db-radio.d.ts +3 -0
- package/dist/esm/components/db-radio.js +1 -1
- package/dist/esm/components/db-range-slider.d.ts +35 -7
- package/dist/esm/components/db-range-slider.js +1 -1
- package/dist/esm/components/db-select.d.ts +35 -6
- package/dist/esm/components/db-select.js +1 -1
- package/dist/esm/components/db-tabs.d.ts +36 -13
- package/dist/esm/components/db-tabs.js +1 -1
- package/dist/esm/components/db-textarea.d.ts +13 -1
- package/dist/esm/components/db-textarea.js +1 -1
- package/dist/esm/components/db-toggle.d.ts +33 -4
- package/dist/esm/components/db-toggle.js +1 -1
- package/dist/esm/components/db-two-factor.d.ts +35 -12
- package/dist/esm/components/db-two-factor.js +1 -1
- package/dist/esm/core/control-label.js +1 -0
- package/dist/esm/core/i18n-copy.js +1 -1
- package/dist/esm/core/range-values.js +1 -0
- package/dist/esm/core/text-field-state.js +1 -0
- package/dist/esm/node_modules/@designbasekorea/ui-contracts/dist/components.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +362 -77
- package/dist/index.esm.js +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +5431 -634
- package/dist/react/index.esm.js +1 -1
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# 변경 이력
|
|
2
2
|
|
|
3
|
+
## 0.10.0 - 2026-10-29
|
|
4
|
+
|
|
5
|
+
- Navbar·PageHeader의 반응형 배치, sticky 레이어 및 메뉴 표시 개선.
|
|
6
|
+
- 폼 컨트롤과 Button·Accordion·Tabs의 포커스, 키보드, 접근성·상태 처리 보강.
|
|
7
|
+
- React Accordion·Tabs의 패널 콘텐츠와 내부 상태 보존, 이벤트 연결 개선. React wrapper 사용 시 React와 React DOM을 함께 설치하세요.
|
|
8
|
+
- 최신 컴포넌트·스타일·타입 및 공통 계약 ui-contracts 0.2.0을 함께 제공.
|
|
9
|
+
|
|
10
|
+
|
|
3
11
|
## 0.9.4 - 2026-10-27
|
|
4
12
|
|
|
5
13
|
Workspace 템플릿 인박스·프로젝트 탭에서 발견한 레이아웃 버그 수정 릴리스입니다.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
type ExpandMode = 'multiple' | 'single' | 'all';
|
|
1
2
|
interface AccordionItemConfig {
|
|
2
3
|
id?: string;
|
|
3
4
|
title: string;
|
|
4
|
-
/** 슬롯(content-N)이 없을 때 본문으로 렌더되는 텍스트 */
|
|
5
5
|
content?: string;
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
itemType?: 'icon' | 'number' | 'question' | 'none';
|
|
@@ -9,28 +9,39 @@ interface AccordionItemConfig {
|
|
|
9
9
|
}
|
|
10
10
|
declare class DbAccordion extends HTMLElement {
|
|
11
11
|
static get observedAttributes(): string[];
|
|
12
|
+
private readonly uid;
|
|
13
|
+
private readonly panels;
|
|
14
|
+
private readonly slotNodes;
|
|
12
15
|
private expanded;
|
|
13
|
-
private
|
|
14
|
-
private
|
|
16
|
+
private rendering;
|
|
17
|
+
private reflecting;
|
|
15
18
|
private childObserver?;
|
|
19
|
+
private stash?;
|
|
16
20
|
get items(): AccordionItemConfig[];
|
|
17
21
|
set items(value: AccordionItemConfig[] | string);
|
|
22
|
+
get size(): string;
|
|
23
|
+
set size(value: string);
|
|
24
|
+
get variant(): string;
|
|
25
|
+
set variant(value: string);
|
|
18
26
|
get allowMultiple(): boolean;
|
|
19
27
|
set allowMultiple(value: boolean);
|
|
28
|
+
get expandMode(): ExpandMode;
|
|
29
|
+
set expandMode(value: ExpandMode);
|
|
20
30
|
get expandedItems(): string[];
|
|
21
31
|
set expandedItems(value: string[] | string);
|
|
22
32
|
connectedCallback(): void;
|
|
23
33
|
disconnectedCallback(): void;
|
|
24
|
-
attributeChangedCallback(name: string): void;
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
attributeChangedCallback(name: string, oldValue: string | null, value: string | null): void;
|
|
35
|
+
/** Refresh externally supplied slot structure without changing expansion or emitting events. */
|
|
36
|
+
refresh(): void;
|
|
37
|
+
focus(options?: FocusOptions): void;
|
|
38
|
+
private entries;
|
|
28
39
|
private syncExpandedFromAttributes;
|
|
29
|
-
private
|
|
30
|
-
private
|
|
31
|
-
private isItemExpanded;
|
|
40
|
+
private collectSlots;
|
|
41
|
+
private createPanel;
|
|
32
42
|
private syncExpandedState;
|
|
33
43
|
private render;
|
|
44
|
+
private navigate;
|
|
34
45
|
private toggle;
|
|
35
46
|
}
|
|
36
47
|
declare global {
|
|
@@ -40,3 +51,4 @@ declare global {
|
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
export { DbAccordion };
|
|
54
|
+
export type { AccordionItemConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dbText as e}from"../core/i18n.js";import{
|
|
1
|
+
import{dbText as e}from"../core/i18n.js";import{dbUid as t}from"../core/uid.js";import{setHostClasses as s}from"../core/host-class.js";import{createDbIcon as n,isDbIconName as i}from"./db-icons.js";import{coerceToContract as r}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{ACCORDION_SIZES as a,ACCORDION_VARIANTS as o}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";class DbAccordion extends HTMLElement{constructor(){super(...arguments),this.uid=t("db-accordion"),this.panels=new Map,this.slotNodes=new Set,this.expanded=new Set,this.rendering=!1,this.reflecting=!1}static get observedAttributes(){return["items","variant","size","allow-multiple","expand-mode","expanded-items","default-item-type"]}get items(){try{const e=JSON.parse(this.getAttribute("items")||"[]");return Array.isArray(e)?e.filter(e=>!!e&&"object"==typeof e&&"string"==typeof e.title):[]}catch{return[]}}set items(e){this.setAttribute("items","string"==typeof e?e:JSON.stringify(e))}get size(){return r(this.getAttribute("size"),a,"m")}set size(e){this.setAttribute("size",e)}get variant(){return r(this.getAttribute("variant"),o,"default")}set variant(e){this.setAttribute("variant",e)}get allowMultiple(){return this.hasAttribute("allow-multiple")}set allowMultiple(e){this.toggleAttribute("allow-multiple",e)}get expandMode(){return r(this.getAttribute("expand-mode"),["multiple","single","all"],"multiple")}set expandMode(e){this.setAttribute("expand-mode",e)}get expandedItems(){return this.isConnected||this.syncExpandedFromAttributes(),[...this.expanded]}set expandedItems(e){this.setAttribute("expanded-items",Array.isArray(e)?JSON.stringify(e):e)}connectedCallback(){for(const e of["items","size","variant","allowMultiple","expandMode","expandedItems"])if(Object.prototype.hasOwnProperty.call(this,e)){const t=this[e];delete this[e],this[e]=t}this.syncExpandedFromAttributes(),this.render(),this.childObserver??(this.childObserver=new MutationObserver(e=>{if(this.rendering)return;e.some(e=>"attributes"===e.type||Array.from(e.addedNodes).some(e=>e instanceof HTMLElement&&e.parentElement===this&&/^content-\d+$/.test(e.getAttribute("slot")||""))||Array.from(e.removedNodes).some(e=>e instanceof HTMLElement&&(this.slotNodes.has(e)&&!this.contains(e)||e.parentElement!==this&&[...this.panels.values()].some(t=>t.wrapper===e))))&&this.render()})),this.childObserver.observe(this,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["slot"]})}disconnectedCallback(){this.childObserver?.disconnect()}attributeChangedCallback(e,t,s){this.isConnected&&!this.reflecting&&t!==s&&(["items","expand-mode","allow-multiple","expanded-items"].includes(e)&&this.syncExpandedFromAttributes(),"expanded-items"===e?this.syncExpandedState():this.render())}refresh(){this.isConnected&&this.render()}focus(e){[...this.panels.values()].find(e=>!e.trigger.disabled)?.trigger.focus(e)}entries(){const e=new Set;return this.items.map((t,s)=>{const n="string"==typeof t.id&&t.id.length?t.id:String(s);let i=n,r=1;for(;e.has(i);)i=`${n}#${++r}`;return e.add(i),{item:t,index:s,key:i}})}syncExpandedFromAttributes(){const e=this.entries().map(e=>e.key),t=this.getAttribute("expanded-items");let s=[];if(null===t)s="all"===this.expandMode?e:[];else if("all"===t.trim())s=e;else try{const e=JSON.parse(t);s=Array.isArray(e)?e.filter(e=>"string"==typeof e||"number"==typeof e).map(String):t.split(",").map(e=>e.trim()).filter(Boolean)}catch{s=t.split(",").map(e=>e.trim()).filter(Boolean)}const n=[...new Set(s)].filter(t=>e.includes(t));this.expanded=new Set("single"===this.expandMode?n.slice(0,1):n)}collectSlots(){for(const e of Array.from(this.children))e instanceof HTMLElement&&/^content-\d+$/.test(e.getAttribute("slot")||"")&&this.slotNodes.add(e);const e=new Map;for(const t of this.slotNodes){if(!this.contains(t)){this.slotNodes.delete(t);continue}const s=t.getAttribute("slot")?.match(/^content-(\d+)$/);if(!s)continue;const n=Number(s[1]),i=e.get(n)||[];i.push(t),e.set(n,i)}return e}createPanel(e){const s=document.createElement("div");s.className="designbase-wc-accordion__item",s.dataset.itemKey=e;const i=document.createElement("button");i.type="button",i.className="designbase-wc-accordion__trigger";const r=document.createElement("div");r.className="designbase-wc-accordion__content";const a=t(this.uid);i.id=`${a}-trigger`,r.id=`${a}-content`,i.setAttribute("aria-controls",r.id),r.setAttribute("role","region"),r.setAttribute("aria-labelledby",i.id);const o=document.createElement("span");o.setAttribute("aria-hidden","true");const d=document.createElement("span");d.className="designbase-wc-accordion__title";const c=document.createElement("span");c.className="designbase-wc-accordion__chevron",c.setAttribute("aria-hidden","true"),c.append(n("chevron-down",20)),i.append(o,d,c),i.addEventListener("click",()=>this.toggle(e)),i.addEventListener("keydown",t=>this.navigate(t,e));const l=document.createElement("div");return l.className="designbase-wc-accordion__content-inner",l.id=`${a}-inner`,r.append(l),s.append(i,r),{wrapper:s,trigger:i,title:d,prefix:o,content:r,inner:l}}syncExpandedState(){for(const[e,t]of this.panels){const s=this.expanded.has(e);!s&&t.content.contains(document.activeElement)&&t.trigger.focus(),t.wrapper.classList.toggle("designbase-wc-accordion__item--expanded",s),t.trigger.setAttribute("aria-expanded",String(s)),t.content.setAttribute("aria-hidden",String(!s)),t.content.inert=!s}}render(){if(!this.rendering){this.rendering=!0;try{const t=this.collectSlots(),a=this.entries(),o=document.activeElement,d=[...this.panels.values()].findIndex(e=>e.wrapper.contains(o)),c=new Set(a.map(e=>e.key));s(this,["designbase-wc-accordion",`designbase-wc-accordion--${this.variant}`,`designbase-wc-accordion--${this.size}`,`designbase-wc-accordion--${this.expandMode}`]),this.setAttribute("role","region"),this.hasAttribute("aria-label")||this.hasAttribute("aria-labelledby")||this.setAttribute("aria-label",e("아코디언"));const l=new Set;let h=null;for(const{item:e,index:s,key:o}of a){const a=this.panels.get(o)||this.createPanel(o);this.panels.set(o,a),a.wrapper.className=`designbase-wc-accordion__item designbase-wc-accordion__item--${this.variant}`,a.wrapper.classList.toggle("designbase-wc-accordion__item--disabled",Boolean(e.disabled)),a.trigger.disabled=Boolean(e.disabled),a.title.textContent=e.title,a.inner.dataset.dbAccordionContentIndex=String(s);const d=r(e.itemType||this.getAttribute("default-item-type"),["icon","number","question","none"],"none"),c="s"===this.size?16:"l"===this.size?24:20,p=`${d}:${e.icon}:${s}:${c}`;if(a.iconSignature!==p){if(a.prefix.replaceChildren(),a.prefix.hidden="none"===d||!["icon","number","question"].includes(d),a.prefix.className=`designbase-wc-accordion__${d}`,"number"===d)a.prefix.textContent=`${s+1}.`;else if("question"===d)a.prefix.textContent="Q.";else if("icon"===d){const t=e.icon;"string"==typeof t&&i(t)&&a.prefix.append(n(t,c))}a.iconSignature=p}if(!a.inner.hasAttribute("data-db-react-content")){const n=t.get(s)||[];for(const e of Array.from(a.inner.children))e===a.fallback||n.includes(e)||(this.stash??(this.stash=Object.assign(document.createElement("div"),{hidden:!0,inert:!0})),this.stash.parentElement||this.append(this.stash),this.stash.append(e));if(n.length){a.fallback?.remove();for(const e of n)l.add(e),e.parentElement!==a.inner&&a.inner.append(e)}else void 0!==e.content&&""!==e.content?(a.fallback??(a.fallback=Object.assign(document.createElement("p"),{className:"designbase-wc-accordion__content-text"})),a.fallback.dataset.dbGenerated="",a.fallback.textContent="string"==typeof e.content?e.content:String(e.content),a.fallback.parentElement!==a.inner&&a.inner.append(a.fallback)):a.fallback?.remove()}const m=h?h.nextElementSibling:this.firstElementChild;m!==a.wrapper&&this.insertBefore(a.wrapper,m),h=a.wrapper}for(const[e,t]of this.panels)if(!c.has(e)){for(const e of this.slotNodes)t.wrapper.contains(e)&&!l.has(e)&&(this.stash??(this.stash=Object.assign(document.createElement("div"),{hidden:!0,inert:!0})),this.stash.parentElement||this.append(this.stash),this.stash.append(e));t.wrapper.remove(),this.panels.delete(e)}for(const e of this.slotNodes)l.has(e)||(this.stash??(this.stash=Object.assign(document.createElement("div"),{hidden:!0,inert:!0})),this.stash.parentElement||this.append(this.stash),e.parentElement!==this.stash&&this.stash.append(e));const p=a.map(({key:e})=>[e,this.panels.get(e)]);this.panels.clear();for(const[e,t]of p)this.panels.set(e,t);if(this.syncExpandedState(),o instanceof HTMLElement&&o!==document.body&&document.activeElement!==o&&d>=0){const e=[...this.panels.values()].filter(e=>!e.trigger.disabled);!o.isConnected||o.closest("[inert]")||o instanceof HTMLButtonElement&&o.disabled?e[Math.min(d,e.length-1)]?.trigger.focus():o.focus()}}finally{this.rendering=!1}this.dispatchEvent(new Event("db-content-targets-change"))}}navigate(e,t){if(!["ArrowDown","ArrowUp","Home","End"].includes(e.key)||e.altKey||e.ctrlKey||e.metaKey)return;const s=[...this.panels.entries()].filter(([,e])=>!e.trigger.disabled),n=s.findIndex(([e])=>e===t);if(n<0)return;e.preventDefault();const i="Home"===e.key?0:"End"===e.key?s.length-1:(n+("ArrowDown"===e.key?1:-1)+s.length)%s.length;s[i]?.[1].trigger.focus()}toggle(e){if(!this.panels.get(e)?.trigger.disabled){this.expanded.has(e)?this.expanded.delete(e):"single"===this.expandMode?this.expanded=new Set([e]):this.expanded.add(e),this.syncExpandedState(),this.reflecting=!0;try{this.expandedItems=[...this.expanded]}finally{this.reflecting=!1}this.dispatchEvent(new CustomEvent("db-change",{detail:{expandedItems:[...this.expanded]},bubbles:!0,composed:!0}))}}}customElements.get("db-accordion")||customElements.define("db-accordion",DbAccordion);export{DbAccordion};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{setHostClasses as t}from"../core/host-class.js";import{createDbIcon as e}from"./db-icons.js";import{AVATAR_SIZES as a,AVATAR_VARIANTS as s,AVATAR_STATUSES as i}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const n=["#E3F2FD","#F3E5F5","#E8F5E8","#FFF3E0","#FCE4EC","#E0F2F1","#FFF8E1","#F1F8E9","#E8EAF6","#FFEBEE"],r=["#1976D2","#7B1FA2","#388E3C","#F57C00","#C2185B","#00796B","#F9A825","#689F38","#3F51B5","#D32F2F"];class DbAvatar extends HTMLElement{constructor(){super(...arguments),this.imageFailed=!1,this.imageLoaded=!1,this.handleClick=()=>{!this.hasAttribute("disabled")&&this.hasAttribute("clickable")&&this.dispatchEvent(new CustomEvent("db-click",{bubbles:!0,composed:!0}))},this.handleKeyDown=t=>{this.hasAttribute("clickable")&&!this.hasAttribute("disabled")&&("Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.handleClick()))}}static get observedAttributes(){return["src","alt","initials","size","variant","status","disabled","clickable","badge","badge-max-count","badge-variant","badge-type","badge-text"]}connectedCallback(){this.render(),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeyDown)}disconnectedCallback(){this.removeEventListener("click",this.handleClick),this.removeEventListener("keydown",this.handleKeyDown)}attributeChangedCallback(t){"src"===t&&(this.imageFailed=!1,this.imageLoaded=!1),this.render()}render(){const d=a.includes(this.getAttribute("size")||"m")&&this.getAttribute("size")||"m",c=s.includes(this.getAttribute("variant")||"default")&&this.getAttribute("variant")||"default",l=this.hasAttribute("disabled"),o=this.hasAttribute("clickable"),b=this.getAttribute("src"),h=this.getAttribute("initials")||"",u=Boolean(b&&!this.imageFailed),g=u&&!this.imageLoaded,m=["designbase-wc-avatar",`designbase-wc-avatar--${d}`,`designbase-wc-avatar--${c}`,o&&"designbase-wc-avatar--clickable",l&&"designbase-wc-avatar--disabled",g&&"designbase-wc-avatar--loading"];if(t(this,m),o&&!l?(this.setAttribute("role","button"),this.tabIndex=0):(this.removeAttribute("role"),this.removeAttribute("tabindex")),this.toggleAttribute("aria-disabled",l),this.replaceChildren(),g){const t=document.createElement("div");t.className="designbase-wc-avatar__loading";const e=document.createElement("div");e.className="designbase-wc-avatar__skeleton",t.append(e),this.append(t)}if(u){const e=document.createElement("img");e.className="designbase-wc-avatar__image",e.src=b,e.alt=this.getAttribute("alt")||"아바타 이미지",e.loading="lazy",e.addEventListener("load",()=>{this.imageLoaded=!0,t(this,m.filter(t=>"designbase-wc-avatar--loading"!==t)),this.querySelector(".designbase-wc-avatar__loading")?.remove()}),e.addEventListener("error",()=>{this.imageFailed=!0,this.imageLoaded=!1,this.render()}),this.append(e)}else if(h){const t=document.createElement("div");if(t.className="designbase-wc-avatar__initials","default"===c){const e=function initialsColor(t){let e=0;for(let a=0;a<t.length;a++)e=t.charCodeAt(a)+((e<<5)-e);const a=Math.abs(e)%n.length;return{backgroundColor:n[a],color:r[a]}}(h);t.style.backgroundColor=e.backgroundColor,t.style.color=e.color}t.textContent=h.charAt(0).toUpperCase(),this.append(t)}else{const t=document.createElement("div");t.className="designbase-wc-avatar__icon",t.append(e("user",function iconSize(t){return"xs"===t?12:"s"===t?16:"m"===t?20:"l"===t?24:"xl"===t?32:40}(d))),this.append(t)}const v=this.getAttribute("status")||"";if(i.includes(v)){const t=document.createElement("span");t.className=`designbase-wc-avatar__status designbase-wc-avatar__status--${v}`,this.append(t)}if(this.hasAttribute("badge")){const t=document.createElement("div");t.className="designbase-wc-avatar__badge";const e=document.createElement("db-badge");e.setAttribute("count",this.getAttribute("badge")||"0");const a=this.getAttribute("badge-type")||"number";e.setAttribute("type",a),this.hasAttribute("badge-max-count")&&e.setAttribute("max-count",this.getAttribute("badge-max-count")),e.setAttribute("variant",this.getAttribute("badge-variant")||"danger"),this.hasAttribute("badge-text")&&(e.textContent=this.getAttribute("badge-text"));const s="xl"===d||"2xl"===d?"m":"s";e.setAttribute("size",s),t.append(e),this.append(t)}}}customElements.get("db-avatar")||customElements.define("db-avatar",DbAvatar);export{DbAvatar};
|
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
* their renderer. This keeps React/Vue slots stable while sharing one element.
|
|
6
6
|
*/
|
|
7
7
|
declare class DbButton extends HTMLElement {
|
|
8
|
+
static readonly formAssociated = true;
|
|
9
|
+
private readonly internals;
|
|
10
|
+
private formDisabled;
|
|
11
|
+
private spacePressed;
|
|
12
|
+
private syncing;
|
|
13
|
+
private pendingSync;
|
|
14
|
+
private managedLoadingLabel;
|
|
15
|
+
private actionEpoch;
|
|
16
|
+
private implicitSubmitter?;
|
|
17
|
+
private childObserver?;
|
|
18
|
+
private desiredTabIndex;
|
|
8
19
|
static get observedAttributes(): string[];
|
|
9
20
|
/**
|
|
10
21
|
* 계약에 없는 값이 attribute 로 들어오면 기본값으로 되돌린다 — throw 하지 않는다.
|
|
@@ -21,6 +32,24 @@ declare class DbButton extends HTMLElement {
|
|
|
21
32
|
set accentStyle(value: string);
|
|
22
33
|
get type(): string;
|
|
23
34
|
set type(value: string);
|
|
35
|
+
get form(): HTMLFormElement | null;
|
|
36
|
+
get name(): string;
|
|
37
|
+
set name(value: string);
|
|
38
|
+
get value(): string;
|
|
39
|
+
set value(value: string);
|
|
40
|
+
get formNoValidate(): boolean;
|
|
41
|
+
set formNoValidate(value: boolean);
|
|
42
|
+
get formAction(): string;
|
|
43
|
+
set formAction(value: string);
|
|
44
|
+
get formMethod(): string;
|
|
45
|
+
set formMethod(value: string);
|
|
46
|
+
get formEnctype(): string;
|
|
47
|
+
set formEnctype(value: string);
|
|
48
|
+
get formTarget(): string;
|
|
49
|
+
set formTarget(value: string);
|
|
50
|
+
private get blocked();
|
|
51
|
+
formDisabledCallback(disabled: boolean): void;
|
|
52
|
+
click(): void;
|
|
24
53
|
get href(): string;
|
|
25
54
|
set href(value: string);
|
|
26
55
|
get loadingText(): string;
|
|
@@ -35,14 +64,17 @@ declare class DbButton extends HTMLElement {
|
|
|
35
64
|
set fullWidth(value: boolean);
|
|
36
65
|
connectedCallback(): void;
|
|
37
66
|
disconnectedCallback(): void;
|
|
38
|
-
attributeChangedCallback(): void;
|
|
67
|
+
attributeChangedCallback(name?: string, oldValue?: string | null, value?: string | null): void;
|
|
39
68
|
private handleClick;
|
|
69
|
+
private followLink;
|
|
40
70
|
private handleKeyDown;
|
|
41
71
|
private handleKeyUp;
|
|
72
|
+
private handleBlur;
|
|
42
73
|
private handlePointerDown;
|
|
43
74
|
private handlePointerUp;
|
|
44
75
|
private syncAttributes;
|
|
45
76
|
private getIconSize;
|
|
77
|
+
private syncImplicitSubmitter;
|
|
46
78
|
private resolveIconName;
|
|
47
79
|
private isManagedIconNode;
|
|
48
80
|
private syncIcons;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isDbIconName as t,createDbIcon as e}from"./db-icons.js";import{sanitizeHref as s}from"../core/safe-url.js";import{coerceWithAliases as i,coerceToContract as n}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{BUTTON_RADII as r,BUTTON_SIZES as a,BUTTON_ACCENT_STYLES as c,BUTTON_VARIANTS as o,BUTTON_VARIANT_ALIASES as d}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const l=["disabled","loading","icon-only","full-width"];class DbButton extends HTMLElement{constructor(){super(...arguments),this.handleClick=t=>{if(this.disabled||this.loading)return t.preventDefault(),void t.stopPropagation();this.dispatchEvent(new CustomEvent("db-click",{bubbles:!0,composed:!0}));const e=this.href;if(e){"_blank"===(this.getAttribute("target")||"_self")?window.open(e,"_blank","noopener,noreferrer"):window.location.assign(e)}},this.handleKeyDown=t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.disabled||this.loading||this.classList.add("designbase-wc-button--pressed"),this.click())},this.handleKeyUp=t=>{"Enter"!==t.key&&" "!==t.key||this.classList.remove("designbase-wc-button--pressed")},this.handlePointerDown=()=>{this.disabled||this.loading||this.classList.add("designbase-wc-button--pressed")},this.handlePointerUp=()=>{this.classList.remove("designbase-wc-button--pressed")}}static get observedAttributes(){return["variant","size","radius","accent-style","type","href","loading-text","start-icon","end-icon",...l]}get variant(){return i(this.getAttribute("variant"),o,d,"primary")}set variant(t){this.setAttribute("variant",t)}get size(){return n(this.getAttribute("size"),a,"m")}set size(t){this.setAttribute("size",t)}get radius(){const t=this.getAttribute("radius");return t?n(t,r,r[0]):""}set radius(t){t?this.setAttribute("radius",t):this.removeAttribute("radius")}get accentStyle(){return n(this.getAttribute("accent-style"),c,"solid")}set accentStyle(t){t?this.setAttribute("accent-style",t):this.removeAttribute("accent-style")}get type(){return this.getAttribute("type")||"button"}set type(t){this.setAttribute("type",t)}get href(){return s(this.getAttribute("href"),"")}set href(t){t?this.setAttribute("href",t):this.removeAttribute("href")}get loadingText(){return this.getAttribute("loading-text")||""}set loadingText(t){t?this.setAttribute("loading-text",t):this.removeAttribute("loading-text")}get disabled(){return this.hasAttribute("disabled")}set disabled(t){this.toggleAttribute("disabled",t)}get loading(){return this.hasAttribute("loading")}set loading(t){this.toggleAttribute("loading",t)}get iconOnly(){return this.hasAttribute("icon-only")}set iconOnly(t){this.toggleAttribute("icon-only",t)}get fullWidth(){return this.hasAttribute("full-width")}set fullWidth(t){this.toggleAttribute("full-width",t)}connectedCallback(){this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeyDown),this.addEventListener("keyup",this.handleKeyUp),this.addEventListener("pointerdown",this.handlePointerDown),this.addEventListener("pointerup",this.handlePointerUp),this.addEventListener("pointerleave",this.handlePointerUp),this.addEventListener("pointercancel",this.handlePointerUp),this.syncAttributes()}disconnectedCallback(){this.removeEventListener("click",this.handleClick),this.removeEventListener("keydown",this.handleKeyDown),this.removeEventListener("keyup",this.handleKeyUp),this.removeEventListener("pointerdown",this.handlePointerDown),this.removeEventListener("pointerup",this.handlePointerUp),this.removeEventListener("pointerleave",this.handlePointerUp),this.removeEventListener("pointercancel",this.handlePointerUp)}attributeChangedCallback(){this.syncAttributes()}syncAttributes(){this.classList.add("designbase-wc-button"),this.setAttribute("role","button"),this.setAttribute("aria-disabled",String(this.disabled||this.loading)),this.loading?this.setAttribute("aria-busy","true"):this.removeAttribute("aria-busy"),this.disabled||this.loading?this.removeAttribute("tabindex"):this.hasAttribute("tabindex")||this.setAttribute("tabindex","0"),this.syncModifier("variant",this.variant),this.syncModifier("size",this.size),this.syncRadius(),this.syncAccentStyle(),this.syncAccentField(),this.syncAccentRing(),this.syncBooleanModifier("disabled"),this.syncBooleanModifier("icon-only"),this.syncBooleanModifier("full-width"),this.syncLoadingVisual(),this.loading?(this.querySelector(":scope > .designbase-wc-button__start-icon")?.remove(),this.querySelector(":scope > .designbase-wc-button__end-icon")?.remove()):this.syncIcons()}getIconSize(){return"s"===this.size?16:"l"===this.size?24:20}resolveIconName(e){if(e&&t(e))return e}isManagedIconNode(t){return t instanceof HTMLElement&&(t.classList.contains("designbase-wc-button__start-icon")||t.classList.contains("designbase-wc-button__end-icon")||t.classList.contains("designbase-wc-button__spinner")||t.classList.contains("designbase-wc-button__accent-field")||t.classList.contains("designbase-wc-button__accent-glass")||t.classList.contains("designbase-wc-button__accent-orb")||t.classList.contains("designbase-wc-button__accent-ring"))}syncIcons(){const t=this.resolveIconName(this.getAttribute("start-icon")),s=this.resolveIconName(this.getAttribute("end-icon")),i=this.getIconSize();let n=this.querySelector(":scope > .designbase-wc-button__start-icon");if(t){if(!n){n=document.createElement("span"),n.className="designbase-wc-button__start-icon",n.setAttribute("aria-hidden","true");const t=Array.from(this.childNodes).find(t=>!this.isManagedIconNode(t));t?this.insertBefore(n,t):this.append(n)}n.replaceChildren(e(t,i))}else n?.remove();let r=this.querySelector(":scope > .designbase-wc-button__end-icon");s?(r||(r=document.createElement("span"),r.className="designbase-wc-button__end-icon",r.setAttribute("aria-hidden","true")),r.replaceChildren(e(s,i)),this.append(r)):r?.remove()}getDefaultRadiusClass(){if(r.includes(this.radius))return`designbase-wc-button--radius-${this.radius}`;const t=a.includes(this.size)?this.size:"m";return`designbase-wc-button--radius-${"s"===t||"l"===t?t:"m"}`}syncRadius(){r.forEach(t=>this.classList.remove(`designbase-wc-button--radius-${t}`)),this.classList.add(this.getDefaultRadiusClass())}syncAccentStyle(){if(c.forEach(t=>this.classList.remove(`designbase-wc-button--accent-${t}`)),"accent"!==this.variant)return;const t=this.accentStyle,e=c.includes(t)?t:"solid";this.classList.add(`designbase-wc-button--accent-${e}`)}syncAccentRing(){const t="accent"===this.variant&&["outline","prism"].includes(this.accentStyle),e=this.querySelector(":scope > .designbase-wc-button__accent-ring");if(!t)return void e?.remove();if(e)return;const s=document.createElement("span");s.className="designbase-wc-button__accent-ring",s.setAttribute("aria-hidden","true");const i=document.createElement("span");i.className="designbase-wc-button__accent-ring-spin",s.appendChild(i),this.insertBefore(s,this.firstChild)}syncAccentField(){const t="accent"===this.variant&&"gradient"===this.accentStyle,e=this.querySelector(":scope > .designbase-wc-button__accent-field"),s=this.querySelector(":scope > .designbase-wc-button__accent-glass");if(!t)return e?.remove(),void s?.remove();let i=e;if(!i){i=document.createElement("span"),i.className="designbase-wc-button__accent-field",i.setAttribute("aria-hidden","true");for(let t=1;t<=5;t+=1){const e=document.createElement("span");e.className=`designbase-wc-button__accent-orb designbase-wc-button__accent-orb--${t}`,i.appendChild(e)}this.insertBefore(i,this.firstChild)}if(!s){const t=document.createElement("span");t.className="designbase-wc-button__accent-glass",t.setAttribute("aria-hidden","true"),i.insertAdjacentElement("afterend",t)}}syncModifier(t,e){if(Array.from(this.classList).filter(e=>e.startsWith(`designbase-wc-button--${t}-`)).forEach(t=>this.classList.remove(t)),"variant"===t||"size"===t){("variant"===t?o:a).forEach(t=>this.classList.remove(`designbase-wc-button--${t}`)),this.classList.add(`designbase-wc-button--${e}`)}}syncBooleanModifier(t){const e=`designbase-wc-button--${t}`;this.classList.toggle(e,this.hasAttribute(t))}getSpinnerSize(){return a.includes(this.size)?this.size:"m"}syncLoadingVisual(){this.classList.toggle("designbase-wc-button--loading",this.loading);const t=this.querySelector(":scope > .designbase-wc-button__spinner");if(!this.loading)return void t?.remove();const e=`designbase-wc-button__spinner designbase-wc-spinner designbase-wc-spinner--circular designbase-wc-spinner--${this.getSpinnerSize()}`;if(t)return t.className=e,void t.setAttribute("aria-hidden","true");const s=document.createElement("span");s.className=e,s.setAttribute("aria-hidden","true"),s.innerHTML='<svg class="designbase-wc-spinner__circular" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle class="designbase-wc-spinner__circular-track" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"></circle><circle class="designbase-wc-spinner__circular-indicator" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"></circle></svg>',this.insertBefore(s,this.firstChild)}}customElements.get("db-button")||customElements.define("db-button",DbButton);export{DbButton};
|
|
1
|
+
import{isDbIconName as t,createDbIcon as e}from"./db-icons.js";import{sanitizeHref as i}from"../core/safe-url.js";import{coerceWithAliases as s,coerceToContract as n}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{BUTTON_RADII as r,BUTTON_SIZES as a,BUTTON_ACCENT_STYLES as o,BUTTON_VARIANTS as c,BUTTON_VARIANT_ALIASES as l}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const d=["disabled","loading","icon-only","full-width"];class DbButton extends HTMLElement{constructor(){super(...arguments),this.internals=this.attachInternals(),this.formDisabled=!1,this.spacePressed=!1,this.syncing=!1,this.pendingSync=!1,this.managedLoadingLabel=null,this.actionEpoch=0,this.desiredTabIndex=null,this.handleClick=t=>{if(t.target!==this&&t.target instanceof Element&&t.target.closest('button,a,input,select,textarea,[contenteditable="true"]'))return;if(this.blocked)return t.preventDefault(),void t.stopImmediatePropagation();if(t.defaultPrevented)return;const e=this.actionEpoch,i=new CustomEvent("db-click",{bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(i),setTimeout(()=>{if(e!==this.actionEpoch||t.defaultPrevented||i.defaultPrevented||this.blocked||!this.isConnected)return;if(this.href)return void this.followLink(t);const s=this.form;if(!s)return;if("reset"===this.type)return void HTMLFormElement.prototype.reset.call(s);if("submit"!==this.type)return;const n=document.createElement("button");n.type="submit",n.hidden=!0,n.tabIndex=-1,n.name=this.name,n.value=this.value;for(const t of["formaction","formmethod","formenctype","formtarget","formnovalidate"])this.hasAttribute(t)&&n.setAttribute(t,this.getAttribute(t));n.setAttribute("data-db-submitter",""),s.append(n);try{HTMLFormElement.prototype.requestSubmit.call(s,n)}finally{n.remove()}},0)},this.handleKeyDown=t=>{t.target!==this||t.defaultPrevented||t.isComposing||"Enter"!==t.key&&" "!==t.key||this.href&&" "===t.key||(t.preventDefault(),this.blocked||t.repeat||(this.classList.add("designbase-wc-button--pressed")," "===t.key?this.spacePressed=!0:this.click()))},this.handleKeyUp=t=>{if(t.target!==this||"Enter"!==t.key&&" "!==t.key)return;const e=" "===t.key&&this.spacePressed&&!t.defaultPrevented&&!this.blocked;this.handleBlur(),e&&(t.preventDefault(),this.click())},this.handleBlur=()=>{this.spacePressed=!1,this.classList.remove("designbase-wc-button--pressed")},this.handlePointerDown=()=>{this.blocked||this.classList.add("designbase-wc-button--pressed")},this.handlePointerUp=()=>{this.classList.remove("designbase-wc-button--pressed")}}static get observedAttributes(){return["variant","size","radius","accent-style","type","href","loading-text","start-icon","end-icon","tabindex","aria-label","aria-labelledby","form","name","value","formaction","formmethod","formenctype","formtarget","formnovalidate","target","rel","download",...d]}get variant(){return s(this.getAttribute("variant"),c,l,"primary")}set variant(t){this.setAttribute("variant",t)}get size(){return n(this.getAttribute("size"),a,"m")}set size(t){this.setAttribute("size",t)}get radius(){const t=this.getAttribute("radius");return t?n(t,r,r[0]):""}set radius(t){t?this.setAttribute("radius",t):this.removeAttribute("radius")}get accentStyle(){return n(this.getAttribute("accent-style"),o,"solid")}set accentStyle(t){t?this.setAttribute("accent-style",t):this.removeAttribute("accent-style")}get type(){const t=this.getAttribute("type")?.toLowerCase();return"submit"===t||"reset"===t?t:"button"}set type(t){this.setAttribute("type",t)}get form(){return this.internals.form}get name(){return this.getAttribute("name")||""}set name(t){this.setAttribute("name",t)}get value(){return this.getAttribute("value")||""}set value(t){this.setAttribute("value",t)}get formNoValidate(){return this.hasAttribute("formnovalidate")}set formNoValidate(t){this.toggleAttribute("formnovalidate",t)}get formAction(){return this.getAttribute("formaction")||""}set formAction(t){this.setAttribute("formaction",t)}get formMethod(){return this.getAttribute("formmethod")||""}set formMethod(t){this.setAttribute("formmethod",t)}get formEnctype(){return this.getAttribute("formenctype")||""}set formEnctype(t){this.setAttribute("formenctype",t)}get formTarget(){return this.getAttribute("formtarget")||""}set formTarget(t){this.setAttribute("formtarget",t)}get blocked(){return this.disabled||this.loading||this.formDisabled}formDisabledCallback(t){this.formDisabled!==t&&(this.formDisabled=t,this.syncing?this.pendingSync=!0:this.syncAttributes())}click(){this.blocked||super.click()}get href(){return i(this.getAttribute("href"),"")}set href(t){t?this.setAttribute("href",t):this.removeAttribute("href")}get loadingText(){return this.getAttribute("loading-text")||""}set loadingText(t){t?this.setAttribute("loading-text",t):this.removeAttribute("loading-text")}get disabled(){return this.hasAttribute("disabled")}set disabled(t){this.toggleAttribute("disabled",t)}get loading(){return this.hasAttribute("loading")}set loading(t){this.toggleAttribute("loading",t)}get iconOnly(){return this.hasAttribute("icon-only")}set iconOnly(t){this.toggleAttribute("icon-only",t)}get fullWidth(){return this.hasAttribute("full-width")}set fullWidth(t){this.toggleAttribute("full-width",t)}connectedCallback(){for(const t of["variant","size","radius","accentStyle","type","href","loadingText","disabled","loading","iconOnly","fullWidth","name","value","formNoValidate","formAction","formMethod","formEnctype","formTarget"])if(Object.prototype.hasOwnProperty.call(this,t)){const e=this[t];delete this[t],this[t]=e}this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeyDown),this.addEventListener("keyup",this.handleKeyUp),this.addEventListener("pointerdown",this.handlePointerDown),this.addEventListener("pointerup",this.handlePointerUp),this.addEventListener("pointerleave",this.handlePointerUp),this.addEventListener("pointercancel",this.handlePointerUp),this.addEventListener("blur",this.handleBlur),this.syncAttributes(),this.childObserver??(this.childObserver=new MutationObserver(t=>{t.some(t=>Array.from(t.removedNodes).some(t=>this.isManagedIconNode(t)&&t.parentNode!==this))&&this.syncAttributes()})),this.childObserver.observe(this,{childList:!0})}disconnectedCallback(){this.actionEpoch++,this.childObserver?.disconnect(),this.removeEventListener("click",this.handleClick),this.removeEventListener("keydown",this.handleKeyDown),this.removeEventListener("keyup",this.handleKeyUp),this.removeEventListener("pointerdown",this.handlePointerDown),this.removeEventListener("pointerup",this.handlePointerUp),this.removeEventListener("pointerleave",this.handlePointerUp),this.removeEventListener("pointercancel",this.handlePointerUp),this.removeEventListener("blur",this.handleBlur),this.handleBlur()}attributeChangedCallback(t,e,i){this.syncing||t&&e===i||("tabindex"===t&&(this.desiredTabIndex=i??null),"aria-label"===t&&i!==this.managedLoadingLabel&&(this.managedLoadingLabel=null),this.syncAttributes())}followLink(t){const e=document.createElement("a");e.href=this.href,e.hidden=!0;for(const t of["target","download","rel"])this.hasAttribute(t)&&e.setAttribute(t,this.getAttribute(t));"_blank"===e.target&&(e.rel=`${e.rel} noopener noreferrer`.trim()),document.body.append(e);try{e.dispatchEvent(new MouseEvent("click",{bubbles:!0,cancelable:!0,ctrlKey:t.ctrlKey,metaKey:t.metaKey,shiftKey:t.shiftKey,altKey:t.altKey,button:t.button}))}finally{e.remove()}}syncAttributes(){if(!this.syncing){this.syncing=!0;try{this.classList.add("designbase-wc-button"),this.setAttribute("role",this.href?"link":"button"),this.setAttribute("aria-disabled",String(this.blocked)),this.loading?this.setAttribute("aria-busy","true"):this.removeAttribute("aria-busy"),this.setAttribute("tabindex",this.blocked?"-1":this.desiredTabIndex??"0"),this.blocked&&(this.actionEpoch++,this.handleBlur()),this.syncModifier("variant",this.variant),this.syncModifier("size",this.size),this.syncRadius(),this.syncAccentStyle(),this.syncAccentField(),this.syncAccentRing(),this.syncBooleanModifier("disabled"),this.classList.toggle("designbase-wc-button--disabled",this.disabled||this.formDisabled),this.syncBooleanModifier("icon-only"),this.syncBooleanModifier("full-width"),this.syncLoadingVisual(),this.syncImplicitSubmitter(),this.loading?(this.querySelector(":scope > .designbase-wc-button__start-icon")?.remove(),this.querySelector(":scope > .designbase-wc-button__end-icon")?.remove()):this.syncIcons()}finally{this.syncing=!1,this.pendingSync&&(this.pendingSync=!1,this.syncAttributes())}}}getIconSize(){return"s"===this.size?16:"l"===this.size?24:20}syncImplicitSubmitter(){if("submit"!==this.type||this.href)return this.implicitSubmitter?.remove(),void(this.implicitSubmitter=void 0);if(!this.implicitSubmitter){const t=document.createElement("button");t.type="submit",t.hidden=!0,t.style.display="none",t.tabIndex=-1,t.className="designbase-wc-button__submit-proxy",t.setAttribute("aria-hidden","true"),t.setAttribute("data-db-implicit-submitter",""),t.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation(),this.click()}),this.implicitSubmitter=t}this.implicitSubmitter.disabled=this.blocked,this.hasAttribute("form")?this.implicitSubmitter.setAttribute("form",this.getAttribute("form")):this.implicitSubmitter.removeAttribute("form"),this.implicitSubmitter.parentElement!==this&&this.append(this.implicitSubmitter)}resolveIconName(e){if(e&&t(e))return e}isManagedIconNode(t){return t instanceof HTMLElement&&(t.classList.contains("designbase-wc-button__start-icon")||t.classList.contains("designbase-wc-button__end-icon")||t.classList.contains("designbase-wc-button__spinner")||t.classList.contains("designbase-wc-button__loading-label")||t.classList.contains("designbase-wc-button__submit-proxy")||t.classList.contains("designbase-wc-button__accent-field")||t.classList.contains("designbase-wc-button__accent-glass")||t.classList.contains("designbase-wc-button__accent-orb")||t.classList.contains("designbase-wc-button__accent-ring"))}syncIcons(){const t=this.resolveIconName(this.getAttribute("start-icon")),i=this.resolveIconName(this.getAttribute("end-icon")),s=this.getIconSize();let n=this.querySelector(":scope > .designbase-wc-button__start-icon");if(t){if(!n){n=document.createElement("span"),n.className="designbase-wc-button__start-icon",n.setAttribute("aria-hidden","true");const t=Array.from(this.childNodes).find(t=>!this.isManagedIconNode(t));t?this.insertBefore(n,t):this.append(n)}n.replaceChildren(e(t,s))}else n?.remove();let r=this.querySelector(":scope > .designbase-wc-button__end-icon");i?(r||(r=document.createElement("span"),r.className="designbase-wc-button__end-icon",r.setAttribute("aria-hidden","true")),r.replaceChildren(e(i,s)),this.append(r)):r?.remove()}getDefaultRadiusClass(){if(r.includes(this.radius))return`designbase-wc-button--radius-${this.radius}`;const t=a.includes(this.size)?this.size:"m";return`designbase-wc-button--radius-${"s"===t||"l"===t?t:"m"}`}syncRadius(){r.forEach(t=>this.classList.remove(`designbase-wc-button--radius-${t}`)),this.classList.add(this.getDefaultRadiusClass())}syncAccentStyle(){if(o.forEach(t=>this.classList.remove(`designbase-wc-button--accent-${t}`)),"accent"!==this.variant)return;const t=this.accentStyle,e=o.includes(t)?t:"solid";this.classList.add(`designbase-wc-button--accent-${e}`)}syncAccentRing(){const t="accent"===this.variant&&["outline","prism"].includes(this.accentStyle),e=this.querySelector(":scope > .designbase-wc-button__accent-ring");if(!t)return void e?.remove();if(e)return;const i=document.createElement("span");i.className="designbase-wc-button__accent-ring",i.setAttribute("aria-hidden","true");const s=document.createElement("span");s.className="designbase-wc-button__accent-ring-spin",i.appendChild(s),this.insertBefore(i,this.firstChild)}syncAccentField(){const t="accent"===this.variant&&"gradient"===this.accentStyle,e=this.querySelector(":scope > .designbase-wc-button__accent-field"),i=this.querySelector(":scope > .designbase-wc-button__accent-glass");if(!t)return e?.remove(),void i?.remove();let s=e;if(!s){s=document.createElement("span"),s.className="designbase-wc-button__accent-field",s.setAttribute("aria-hidden","true");for(let t=1;t<=5;t+=1){const e=document.createElement("span");e.className=`designbase-wc-button__accent-orb designbase-wc-button__accent-orb--${t}`,s.appendChild(e)}this.insertBefore(s,this.firstChild)}if(!i){const t=document.createElement("span");t.className="designbase-wc-button__accent-glass",t.setAttribute("aria-hidden","true"),s.insertAdjacentElement("afterend",t)}}syncModifier(t,e){if(Array.from(this.classList).filter(e=>e.startsWith(`designbase-wc-button--${t}-`)).forEach(t=>this.classList.remove(t)),"variant"===t||"size"===t){("variant"===t?c:a).forEach(t=>this.classList.remove(`designbase-wc-button--${t}`)),this.classList.add(`designbase-wc-button--${e}`)}}syncBooleanModifier(t){const e=`designbase-wc-button--${t}`;this.classList.toggle(e,this.hasAttribute(t))}getSpinnerSize(){return a.includes(this.size)?this.size:"m"}syncLoadingVisual(){this.classList.toggle("designbase-wc-button--loading",this.loading);const t=this.loading&&!!this.loadingText;this.classList.toggle("designbase-wc-button--loading-text",t);const e=this.getAttribute("aria-label");!t||this.hasAttribute("aria-labelledby")||this.hasAttribute("aria-label")&&e!==this.managedLoadingLabel?null!==this.managedLoadingLabel&&(e===this.managedLoadingLabel&&this.removeAttribute("aria-label"),this.managedLoadingLabel=null):(this.managedLoadingLabel=this.loadingText,this.setAttribute("aria-label",this.loadingText));let i=this.querySelector(":scope > .designbase-wc-button__loading-label");t?(i||(i=document.createElement("span"),i.className="designbase-wc-button__loading-label",i.setAttribute("aria-hidden","true"),this.append(i)),i.textContent=this.loadingText):i?.remove();const s=this.querySelector(":scope > .designbase-wc-button__spinner");if(!this.loading)return void s?.remove();const n=`designbase-wc-button__spinner designbase-wc-spinner designbase-wc-spinner--circular designbase-wc-spinner--${this.getSpinnerSize()}`;if(s)return s.className=n,void s.setAttribute("aria-hidden","true");const r=document.createElement("span");r.className=n,r.setAttribute("aria-hidden","true"),r.innerHTML='<svg class="designbase-wc-spinner__circular" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle class="designbase-wc-spinner__circular-track" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"></circle><circle class="designbase-wc-spinner__circular-indicator" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"></circle></svg>',this.insertBefore(r,this.firstChild)}}DbButton.formAssociated=!0,customElements.get("db-button")||customElements.define("db-button",DbButton);export{DbButton};
|
|
@@ -40,6 +40,10 @@ declare class DbChatOrb extends HTMLElement {
|
|
|
40
40
|
private lastVoiceRipple;
|
|
41
41
|
private previousVoiceLevel;
|
|
42
42
|
private displayedVoiceLevel;
|
|
43
|
+
private dotsMix;
|
|
44
|
+
private layer;
|
|
45
|
+
private customShapes;
|
|
46
|
+
private shapeBuffer;
|
|
43
47
|
private inputStream;
|
|
44
48
|
private audioContext;
|
|
45
49
|
private audioSource;
|
|
@@ -53,6 +57,15 @@ declare class DbChatOrb extends HTMLElement {
|
|
|
53
57
|
set state(v: OrbState);
|
|
54
58
|
get theme(): OrbTheme;
|
|
55
59
|
set theme(v: OrbTheme);
|
|
60
|
+
/** thinking 상태의 표현. `dots` 는 구체가 작아지며 점 3개가 순서대로 튀는 로딩 모션으로 바뀝니다. */
|
|
61
|
+
get thinkingStyle(): 'orb' | 'dots';
|
|
62
|
+
set thinkingStyle(v: 'orb' | 'dots');
|
|
63
|
+
/**
|
|
64
|
+
* morph 로 순환할 모양 목록. 프리셋 이름(circle · squircle · flower · star · triangle · blob)
|
|
65
|
+
* 또는 SVG path 의 d 값을 섞어서 넣을 수 있습니다. 속성 `shape="circle,flower,star"` 로도 지정합니다.
|
|
66
|
+
*/
|
|
67
|
+
get shapes(): string[];
|
|
68
|
+
set shapes(v: string[] | null);
|
|
56
69
|
/** 외부 오디오 분석기가 전달하는 정규화 음성 레벨(0–1). */
|
|
57
70
|
get voiceLevel(): number;
|
|
58
71
|
set voiceLevel(v: number);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{coerceToContract as t}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{CHAT_ORB_SIZES as e,CHAT_ORB_VARIANTS as i,CHAT_ORB_STATES as s,CHAT_ORB_THEMES as a}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const r={aurora:{core:[22,20,52],lights:[[236,92,220],[70,140,250],[42,208,158]],rim:[199,210,254],glow:[120,110,240]},ocean:{core:[10,32,62],lights:[[56,189,248],[34,211,238],[96,165,250]],rim:[165,243,252],glow:[59,130,246]},emerald:{core:[6,42,38],lights:[[52,211,153],[74,222,128],[163,230,53]],rim:[187,247,208],glow:[34,197,94]},violet:{core:[40,12,82],lights:[[192,132,252],[168,85,247],[240,171,252]],rim:[245,208,254],glow:[139,92,246]},mono:{core:[30,34,44],lights:[[229,231,235],[156,163,175],[249,250,251]],rim:[255,255,255],glow:[156,163,175]}},n={aurora:{core:[240,240,250],lights:[[235,96,224],[74,144,246],[26,196,150]],rim:[150,130,210],glow:[138,128,244]},ocean:{core:[226,248,255],lights:[[14,165,233],[34,211,238],[96,165,250]],rim:[64,150,181],glow:[56,189,248]},emerald:{core:[228,250,240],lights:[[16,185,129],[74,222,128],[163,230,53]],rim:[57,145,111],glow:[52,211,153]},violet:{core:[246,233,255],lights:[[168,85,247],[217,70,239],[240,171,252]],rim:[143,91,183],glow:[192,132,252]},mono:{core:[243,246,250],lights:[[148,163,184],[203,213,225],[255,255,255]],rim:[100,116,139],glow:[148,163,184]}},o={idle:{speed:1.55,amp:.38,turb:.95,glow:1.15,breatheSpeed:1.9,breatheAmt:.055,lightAlpha:.95,autoRipple:0},thinking:{speed:3.2,amp:.5,turb:1.45,glow:1.85,breatheSpeed:4.2,breatheAmt:.075,lightAlpha:1.35,autoRipple:0},listening:{speed:1.45,amp:.32,turb:.9,glow:1.2,breatheSpeed:2.1,breatheAmt:.035,lightAlpha:.95,autoRipple:1.7}};function rgba(t,e){return`rgba(${t[0]},${t[1]},${t[2]},${e})`}function mix(t,e,i){return[t[0]+(e[0]-t[0])*i,t[1]+(e[1]-t[1])*i,t[2]+(e[2]-t[2])*i]}function clamp(t,e,i){return Math.min(i,Math.max(e,t))}function numberAttribute(t,e,i,s,a){const r=t.getAttribute(e);if(null===r||""===r.trim())return i;const n=Number(r);return Number.isFinite(n)?clamp(n,s,a):i}const h=2*Math.PI;function prefersReducedMotion(){return"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches}const l=new Set;let c=0;function tick(t){l.forEach(e=>e.frame(t)),c=l.size?requestAnimationFrame(tick):0}class DbChatOrb extends HTMLElement{constructor(){super(...arguments),this.dpr=1,this.cssW=0,this.cssH=0,this.t0=0,this.lastFrame=0,this.resizeObs=null,this.io=null,this.themeObserver=null,this.themeMedia=null,this.visible=!1,this.hover={x:0,y:0,tx:0,ty:0},this.ripples=[],this.lastAutoRipple=0,this.lastVoiceRipple=0,this.previousVoiceLevel=0,this.displayedVoiceLevel=0,this.inputStream=null,this.audioContext=null,this.audioSource=null,this.analyser=null,this.analyserData=null,this.onPointerMove=t=>{const e=this.getBoundingClientRect();e.width&&(this.hover.tx=Math.max(-1,Math.min(1,2*((t.clientX-e.left)/e.width-.5))),this.hover.ty=Math.max(-1,Math.min(1,2*((t.clientY-e.top)/e.height-.5))))},this.onPointerLeave=()=>{this.hover.tx=0,this.hover.ty=0},this.onPointerDown=()=>{this.addRipple(performance.now(),1)},this.onThemeChange=()=>{prefersReducedMotion()&&this.frame(performance.now())}}static get observedAttributes(){return["size","variant","state","theme","voice-level","min-scale","max-scale"]}get size(){return t(this.getAttribute("size"),e,"l")}set size(t){this.setAttribute("size",t)}get variant(){return t(this.getAttribute("variant"),i,"aurora")}set variant(t){this.setAttribute("variant",t)}get state(){return t(this.getAttribute("state"),s,"idle")}set state(t){this.setAttribute("state",t)}get theme(){return t(this.getAttribute("theme"),a,"auto")}set theme(t){this.setAttribute("theme",t)}get voiceLevel(){return numberAttribute(this,"voice-level",0,0,1)}set voiceLevel(t){const e=Number(t);this.setAttribute("voice-level",String(clamp(Number.isFinite(e)?e:0,0,1)))}get minScale(){return numberAttribute(this,"min-scale",.88,.5,1.5)}set minScale(t){const e=Number(t);this.setAttribute("min-scale",String(clamp(Number.isFinite(e)?e:.88,.5,1.5)))}get maxScale(){return Math.max(this.minScale,numberAttribute(this,"max-scale",1.18,.5,1.7))}set maxScale(t){const e=Number(t);this.setAttribute("max-scale",String(clamp(Number.isFinite(e)?e:1.18,.5,1.7)))}get audioStream(){return this.inputStream}set audioStream(t){(t!==this.inputStream||null!==t&&!this.analyser)&&(this.stopAudioAnalysis(),this.inputStream=t&&"function"==typeof t.getAudioTracks?t:null,this.isConnected&&this.inputStream&&this.startAudioAnalysis(this.inputStream))}connectedCallback(){this.syncClasses(),this.setAttribute("aria-hidden","true"),this.canvas||this.build(),this.t0=performance.now(),this.lastFrame=0,this.observeTheme(),this.inputStream&&this.startAudioAnalysis(this.inputStream),"undefined"!=typeof ResizeObserver&&(this.resizeObs=new ResizeObserver(()=>this.resize()),this.resizeObs.observe(this)),this.resize(),prefersReducedMotion()?this.frame(this.t0+1600):"undefined"!=typeof IntersectionObserver?(this.io=new IntersectionObserver(t=>{for(const e of t)this.setVisible(e.isIntersecting)},{threshold:.01}),this.io.observe(this)):this.setVisible(!0)}disconnectedCallback(){this.setVisible(!1),this.resizeObs?.disconnect(),this.resizeObs=null,this.io?.disconnect(),this.io=null,this.themeObserver?.disconnect(),this.themeObserver=null,this.themeMedia?.removeEventListener("change",this.onThemeChange),this.themeMedia=null,this.stopAudioAnalysis()}attributeChangedCallback(t){this.canvas&&("size"!==t&&"variant"!==t&&"state"!==t||this.syncClasses(),prefersReducedMotion()&&this.frame(performance.now()))}setVisible(t){t!==this.visible&&(this.visible=t,t?(l.add(this),function ensureTicking(){c||!l.size||prefersReducedMotion()||(c=requestAnimationFrame(tick))}()):l.delete(this))}syncClasses(){this.className=["designbase-wc-chat-orb",`designbase-wc-chat-orb--${this.size}`,`designbase-wc-chat-orb--${this.variant}`,`designbase-wc-chat-orb--${this.state}`].join(" ")}build(){this.replaceChildren(),this.canvas=document.createElement("canvas"),this.canvas.className="designbase-wc-chat-orb__canvas",this.ctx=this.canvas.getContext("2d"),this.append(this.canvas),this.addEventListener("pointermove",this.onPointerMove),this.addEventListener("pointerleave",this.onPointerLeave),this.addEventListener("pointerdown",this.onPointerDown)}addRipple(t,e){this.ripples.push({start:t,strength:clamp(e,.2,1)})}observeTheme(){"undefined"==typeof MutationObserver||this.themeObserver||(this.themeObserver=new MutationObserver(this.onThemeChange),this.themeObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme","data-color-mode","class"]})),"function"!=typeof window.matchMedia||this.themeMedia||(this.themeMedia=window.matchMedia("(prefers-color-scheme: dark)"),this.themeMedia.addEventListener("change",this.onThemeChange))}isDarkContext(){if("dark"===this.theme)return!0;if("light"===this.theme)return!1;const t=this.parentElement?.closest("[data-theme], [data-color-mode]"),e=t?.getAttribute("data-color-mode");if("dark"===e)return!0;if("light"===e)return!1;const i=t?.getAttribute("data-theme");if("dark"===i)return!0;if("light"===i)return!1;const s=document.documentElement,a=s.getAttribute("data-color-mode");if("dark"===a)return!0;if("light"===a)return!1;const r=s.getAttribute("data-theme");return"dark"===r||"light"!==r&&(this.themeMedia?.matches??!1)}startAudioAnalysis(t){if(this.analyser)return;const e=window.AudioContext??window.webkitAudioContext;if(e)try{const i=new e,s=i.createAnalyser();s.fftSize=512,s.smoothingTimeConstant=.58;const a=i.createMediaStreamSource(t);a.connect(s),this.audioContext=i,this.audioSource=a,this.analyser=s,this.analyserData=new Float32Array(s.fftSize),"suspended"===i.state&&i.resume().catch(()=>{})}catch{this.stopAudioAnalysis()}}stopAudioAnalysis(){try{this.audioSource?.disconnect(),this.analyser?.disconnect()}catch{}this.audioContext&&this.audioContext.close().catch(()=>{}),this.audioContext=null,this.audioSource=null,this.analyser=null,this.analyserData=null}sampleVoiceLevel(t){if(this.analyser&&this.analyserData){this.analyser.getFloatTimeDomainData(this.analyserData);let t=0;for(const e of this.analyserData)t+=e*e;return{level:clamp((Math.sqrt(t/this.analyserData.length)-.01)/.11,0,1),hasInput:!0}}if(this.hasAttribute("voice-level"))return{level:this.voiceLevel,hasInput:!0};return{level:clamp(.16+.12*(Math.sin(2.05*t)+1)+.42*Math.pow(Math.max(0,Math.sin(.86*t+.7)),5),0,1),hasInput:!1}}resize(){this.canvas&&(this.dpr=Math.min(2,window.devicePixelRatio||1),this.cssW=this.clientWidth||72,this.cssH=this.clientHeight||this.cssW,this.canvas.width=Math.round(this.cssW*this.dpr),this.canvas.height=Math.round(this.cssH*this.dpr),prefersReducedMotion()&&this.frame(this.t0+1600))}frame(t){const e=this.ctx;if(!e||!this.cssW)return;const i=this.lastFrame?Math.min((t-this.lastFrame)/1e3,.1):1/60;this.lastFrame=t;const s=(t-this.t0)/1e3,a=this.cssW,l=this.cssH,c=a/2,d=l/2,m=Math.min(a,l)/2,u=this.isDarkContext(),p=u?r[this.variant]:n[this.variant],g=o[this.state];this.hover.x+=.08*(this.hover.tx-this.hover.x),this.hover.y+=.08*(this.hover.ty-this.hover.y);const b="listening"===this.state?this.sampleVoiceLevel(s):{level:0},v=b.level>this.displayedVoiceLevel?19:7;this.displayedVoiceLevel+=(b.level-this.displayedVoiceLevel)*(1-Math.exp(-v*i)),"listening"!==this.state&&this.displayedVoiceLevel<.001&&(this.displayedVoiceLevel=0);const f=Math.pow(clamp(this.displayedVoiceLevel,0,1),.72),y="listening"===this.state?this.minScale+(this.maxScale-this.minScale)*f:1,w=g.breatheAmt*("listening"===this.state?.55:1),M=1+Math.sin(s*g.breatheSpeed)*w,A="listening"===this.state?f:"thinking"===this.state?1:.55,S="thinking"===this.state?1.38:1,C=.68*m*M*y*("thinking"===this.state?1.1:1),x=.99*m;e.setTransform(this.dpr,0,0,this.dpr,0,0),e.clearRect(0,0,a,l),e.globalCompositeOperation="source-over",e.filter="none";const k="thinking"===this.state?u?.42:.22:u?.28:.5,O=u?mix(p.core,p.lights[0],k):"thinking"===this.state?mix(p.core,p.lights[0],.35):mix(p.core,[255,255,255],.5),R=mix(p.core,p.lights[1],u?"thinking"===this.state?.35:0:"thinking"===this.state?.28:.12),z=e.createRadialGradient(c,d-.12*C,.1*C,c,d,C);z.addColorStop(0,rgba(O,u?1:.96)),z.addColorStop(.72,rgba(R,u?1:.9)),z.addColorStop(.93,rgba(R,u?.72:.5)),z.addColorStop(1,rgba(R,0)),e.fillStyle=z,e.beginPath(),e.arc(c,d,C,0,h),e.fill(),e.globalCompositeOperation=u?"lighter":"source-over";const L=p.lights.length;for(let t=0;t<L;t+=1){const i=2.1*t,a=g.turb*(.85+.55*A),r=Math.sin(s*g.speed+i)*g.amp+Math.sin(s*g.speed*1.7+1.4*i)*g.amp*a*.75+Math.sin(s*g.speed*.45+.7*i)*g.amp*.35,n=Math.cos(s*g.speed*.9+1.3*i)*g.amp+Math.cos(s*g.speed*2+i)*g.amp*a*.75+Math.cos(s*g.speed*.55+.9*i)*g.amp*.35,o="thinking"===this.state?.42:.28,l=t/L*h-.5*Math.PI+s*g.speed*o,m=C*("thinking"===this.state?.52:.44),b=c+Math.cos(l)*m+r*C+this.hover.x*C*.12,v=d+Math.sin(l)*m+n*C+this.hover.y*C*.12,f=C*S*(.92+.28*A+.12*Math.sin(s*g.speed*1.3+i)),y="thinking"===this.state?mix(p.lights[t],u?[255,255,255]:p.core,u?.1:.18):p.lights[t],w=(u?.62:.78)*g.lightAlpha*(.85+.28*A)*("thinking"===this.state?1.22:1);e.save(),e.filter=`blur(${C*(u?.15:.18)*("thinking"===this.state?.92:1)}px)`;const M=e.createRadialGradient(b,v,0,b,v,f);M.addColorStop(0,rgba(y,Math.min(1,w))),M.addColorStop(.48,rgba(y,Math.min(1,.5*w))),M.addColorStop(1,rgba(y,0)),e.fillStyle=M,e.beginPath(),e.arc(b,v,f,0,h),e.fill(),e.restore()}e.filter="none",e.globalCompositeOperation="destination-in";const D=e.createRadialGradient(c,d,.6*C,c,d,C);D.addColorStop(0,"rgba(0,0,0,1)"),D.addColorStop(.86,"rgba(0,0,0,1)"),D.addColorStop(1,"rgba(0,0,0,0)"),e.fillStyle=D,e.beginPath(),e.arc(c,d,C,0,h),e.fill(),e.globalCompositeOperation="source-atop";const V=c-.22*C+this.hover.x*C*.1,F=d-.28*C+this.hover.y*C*.1,E=e.createRadialGradient(V,F,0,V,F,.95*C);E.addColorStop(0,`rgba(255,255,255,${u?.12:.2})`),E.addColorStop(1,"rgba(255,255,255,0)"),e.fillStyle=E,e.fillRect(c-C,d-C,2*C,2*C),e.globalCompositeOperation="destination-over";const P=e.createRadialGradient(c,d,.7*C,c,d,x),T=(u?.32:.14)*g.glow*(.85+.28*A);P.addColorStop(0,rgba(p.glow,T)),P.addColorStop(1,rgba(p.glow,0)),e.fillStyle=P,e.beginPath(),e.arc(c,d,x,0,h),e.fill(),e.globalCompositeOperation="source-over",e.filter="none"}}customElements.get("db-chat-orb")||customElements.define("db-chat-orb",DbChatOrb);export{DbChatOrb};
|
|
1
|
+
import{coerceToContract as t}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{CHAT_ORB_SIZES as e,CHAT_ORB_VARIANTS as i,CHAT_ORB_STATES as s,CHAT_ORB_THEMES as a}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const r={aurora:{core:[22,20,52],lights:[[236,92,220],[70,140,250],[42,208,158]],rim:[199,210,254],glow:[120,110,240]},ocean:{core:[10,32,62],lights:[[56,189,248],[34,211,238],[96,165,250]],rim:[165,243,252],glow:[59,130,246]},emerald:{core:[6,42,38],lights:[[52,211,153],[74,222,128],[163,230,53]],rim:[187,247,208],glow:[34,197,94]},violet:{core:[40,12,82],lights:[[192,132,252],[168,85,247],[240,171,252]],rim:[245,208,254],glow:[139,92,246]},mono:{core:[30,34,44],lights:[[229,231,235],[156,163,175],[249,250,251]],rim:[255,255,255],glow:[156,163,175]}},o={aurora:{core:[240,240,250],lights:[[235,96,224],[74,144,246],[26,196,150]],rim:[150,130,210],glow:[138,128,244]},ocean:{core:[226,248,255],lights:[[14,165,233],[34,211,238],[96,165,250]],rim:[64,150,181],glow:[56,189,248]},emerald:{core:[228,250,240],lights:[[16,185,129],[74,222,128],[163,230,53]],rim:[57,145,111],glow:[52,211,153]},violet:{core:[246,233,255],lights:[[168,85,247],[217,70,239],[240,171,252]],rim:[143,91,183],glow:[192,132,252]},mono:{core:[243,246,250],lights:[[148,163,184],[203,213,225],[255,255,255]],rim:[100,116,139],glow:[148,163,184]}},n={idle:{speed:1.55,amp:.38,turb:.95,glow:1.15,breatheSpeed:1.9,breatheAmt:.055,lightAlpha:.95,autoRipple:0},thinking:{speed:3.2,amp:.5,turb:1.45,glow:1.85,breatheSpeed:4.2,breatheAmt:.075,lightAlpha:1.35,autoRipple:0},listening:{speed:1.45,amp:.32,turb:.9,glow:1.2,breatheSpeed:2.1,breatheAmt:.035,lightAlpha:.95,autoRipple:1.7}};function rgba(t,e){return`rgba(${t[0]},${t[1]},${t[2]},${e})`}function mix(t,e,i){return[t[0]+(e[0]-t[0])*i,t[1]+(e[1]-t[1])*i,t[2]+(e[2]-t[2])*i]}function clamp(t,e,i){return Math.min(i,Math.max(e,t))}function numberAttribute(t,e,i,s,a){const r=t.getAttribute(e);if(null===r||""===r.trim())return i;const o=Number(r);return Number.isFinite(o)?clamp(o,s,a):i}const h=2*Math.PI;function prefersReducedMotion(){return"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches}const l=72,c={circle:()=>1,squircle:t=>1/Math.pow(Math.pow(Math.abs(Math.cos(t)),4)+Math.pow(Math.abs(Math.sin(t)),4),.25),flower:t=>1+.16*Math.cos(5*t),star:t=>.78+.22*Math.cos(5*t),triangle:t=>1+.3*Math.cos(3*(t+Math.PI/2)),blob:t=>1+.1*Math.sin(2*t+1)+.08*Math.sin(3*t)};function normalizeShape(t){let e=0;for(let i=0;i<t.length;i+=2)e=Math.max(e,Math.hypot(t[i],t[i+1]));const i=new Float32Array(t.length);for(let s=0;s<t.length;s+=1)i[s]=t[s]/(e||1);return i}function presetShape(t){const e=c[t];if(!e)return null;const i=[];for(let t=0;t<l;t+=1){const s=t/l*h,a=e(s);i.push(Math.cos(s)*a,Math.sin(s)*a)}return normalizeShape(i)}const d=new Map;function resolveShape(t){const e=t.trim(),i=d.get(e);if(i)return i;const s=(/^[Mm]/.test(e)?function svgPathShape(t){try{const e=document.createElementNS("http://www.w3.org/2000/svg","path");e.setAttribute("d",t);const i=e.getTotalLength();if(!(i>0))return null;const s=[];for(let t=0;t<l;t+=1){const a=e.getPointAtLength(i*t/l);s.push([a.x,a.y])}const a=s.map(t=>t[0]),r=s.map(t=>t[1]),o=(Math.min(...a)+Math.max(...a))/2,n=(Math.min(...r)+Math.max(...r))/2;let h=s.map(([t,e])=>[t-o,e-n]),c=0;for(let t=0;t<h.length;t+=1){const[e,i]=h[t],[s,a]=h[(t+1)%h.length];c+=e*a-s*i}c<0&&(h=h.reverse());let d=0,m=1/0;return h.forEach(([t,e],i)=>{const s=Math.abs(Math.atan2(e,t));s<m&&(m=s,d=i)}),h=[...h.slice(d),...h.slice(0,d)],normalizeShape(h.flat())}catch{return null}}(e):presetShape(e.toLowerCase()))??presetShape("circle");return d.set(e,s),s}function smoothstep(t,e,i){const s=clamp((i-t)/(e-t),0,1);return s*s*(3-2*s)}const m=new Set;let u=0;function tick(t){m.forEach(e=>e.frame(t)),u=m.size?requestAnimationFrame(tick):0}class DbChatOrb extends HTMLElement{constructor(){super(...arguments),this.dpr=1,this.cssW=0,this.cssH=0,this.t0=0,this.lastFrame=0,this.resizeObs=null,this.io=null,this.themeObserver=null,this.themeMedia=null,this.visible=!1,this.hover={x:0,y:0,tx:0,ty:0},this.ripples=[],this.lastAutoRipple=0,this.lastVoiceRipple=0,this.previousVoiceLevel=0,this.displayedVoiceLevel=0,this.dotsMix=0,this.layer=null,this.customShapes=null,this.shapeBuffer=new Float32Array(144),this.inputStream=null,this.audioContext=null,this.audioSource=null,this.analyser=null,this.analyserData=null,this.onPointerMove=t=>{const e=this.getBoundingClientRect();e.width&&(this.hover.tx=Math.max(-1,Math.min(1,2*((t.clientX-e.left)/e.width-.5))),this.hover.ty=Math.max(-1,Math.min(1,2*((t.clientY-e.top)/e.height-.5))))},this.onPointerLeave=()=>{this.hover.tx=0,this.hover.ty=0},this.onPointerDown=()=>{this.addRipple(performance.now(),1)},this.onThemeChange=()=>{prefersReducedMotion()&&this.frame(performance.now())}}static get observedAttributes(){return["size","variant","state","theme","voice-level","min-scale","max-scale","thinking-style","shape","morph-interval"]}get size(){return t(this.getAttribute("size"),e,"l")}set size(t){this.setAttribute("size",t)}get variant(){return t(this.getAttribute("variant"),i,"aurora")}set variant(t){this.setAttribute("variant",t)}get state(){return t(this.getAttribute("state"),s,"idle")}set state(t){this.setAttribute("state",t)}get theme(){return t(this.getAttribute("theme"),a,"auto")}set theme(t){this.setAttribute("theme",t)}get thinkingStyle(){return"dots"===this.getAttribute("thinking-style")?"dots":"orb"}set thinkingStyle(t){this.setAttribute("thinking-style",t)}get shapes(){if(this.customShapes?.length)return this.customShapes;const t=(this.getAttribute("shape")??"").trim();return t?/^[Mm]/.test(t)?[t]:t.split(",").map(t=>t.trim()).filter(Boolean):["circle"]}set shapes(t){this.customShapes=Array.isArray(t)?t.filter(t=>"string"==typeof t&&t.trim()):null,prefersReducedMotion()&&this.canvas&&this.frame(performance.now())}get voiceLevel(){return numberAttribute(this,"voice-level",0,0,1)}set voiceLevel(t){const e=Number(t);this.setAttribute("voice-level",String(clamp(Number.isFinite(e)?e:0,0,1)))}get minScale(){return numberAttribute(this,"min-scale",.88,.5,1.5)}set minScale(t){const e=Number(t);this.setAttribute("min-scale",String(clamp(Number.isFinite(e)?e:.88,.5,1.5)))}get maxScale(){return Math.max(this.minScale,numberAttribute(this,"max-scale",1.18,.5,1.7))}set maxScale(t){const e=Number(t);this.setAttribute("max-scale",String(clamp(Number.isFinite(e)?e:1.18,.5,1.7)))}get audioStream(){return this.inputStream}set audioStream(t){(t!==this.inputStream||null!==t&&!this.analyser)&&(this.stopAudioAnalysis(),this.inputStream=t&&"function"==typeof t.getAudioTracks?t:null,this.isConnected&&this.inputStream&&this.startAudioAnalysis(this.inputStream))}connectedCallback(){this.syncClasses(),this.setAttribute("aria-hidden","true"),this.canvas||this.build(),this.t0=performance.now(),this.lastFrame=0,this.observeTheme(),this.inputStream&&this.startAudioAnalysis(this.inputStream),"undefined"!=typeof ResizeObserver&&(this.resizeObs=new ResizeObserver(()=>this.resize()),this.resizeObs.observe(this)),this.resize(),prefersReducedMotion()?this.frame(this.t0+1600):"undefined"!=typeof IntersectionObserver?(this.io=new IntersectionObserver(t=>{for(const e of t)this.setVisible(e.isIntersecting)},{threshold:.01}),this.io.observe(this)):this.setVisible(!0)}disconnectedCallback(){this.setVisible(!1),this.resizeObs?.disconnect(),this.resizeObs=null,this.io?.disconnect(),this.io=null,this.themeObserver?.disconnect(),this.themeObserver=null,this.themeMedia?.removeEventListener("change",this.onThemeChange),this.themeMedia=null,this.stopAudioAnalysis()}attributeChangedCallback(t){this.canvas&&("size"!==t&&"variant"!==t&&"state"!==t||this.syncClasses(),prefersReducedMotion()&&this.frame(performance.now()))}setVisible(t){t!==this.visible&&(this.visible=t,t?(m.add(this),function ensureTicking(){u||!m.size||prefersReducedMotion()||(u=requestAnimationFrame(tick))}()):m.delete(this))}syncClasses(){this.className=["designbase-wc-chat-orb",`designbase-wc-chat-orb--${this.size}`,`designbase-wc-chat-orb--${this.variant}`,`designbase-wc-chat-orb--${this.state}`].join(" ")}build(){this.replaceChildren(),this.canvas=document.createElement("canvas"),this.canvas.className="designbase-wc-chat-orb__canvas",this.ctx=this.canvas.getContext("2d"),this.append(this.canvas),this.addEventListener("pointermove",this.onPointerMove),this.addEventListener("pointerleave",this.onPointerLeave),this.addEventListener("pointerdown",this.onPointerDown)}addRipple(t,e){this.ripples.push({start:t,strength:clamp(e,.2,1)})}observeTheme(){"undefined"==typeof MutationObserver||this.themeObserver||(this.themeObserver=new MutationObserver(this.onThemeChange),this.themeObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme","data-color-mode","class"]})),"function"!=typeof window.matchMedia||this.themeMedia||(this.themeMedia=window.matchMedia("(prefers-color-scheme: dark)"),this.themeMedia.addEventListener("change",this.onThemeChange))}isDarkContext(){if("dark"===this.theme)return!0;if("light"===this.theme)return!1;const t=this.parentElement?.closest("[data-theme], [data-color-mode]"),e=t?.getAttribute("data-color-mode");if("dark"===e)return!0;if("light"===e)return!1;const i=t?.getAttribute("data-theme");if("dark"===i)return!0;if("light"===i)return!1;const s=document.documentElement,a=s.getAttribute("data-color-mode");if("dark"===a)return!0;if("light"===a)return!1;const r=s.getAttribute("data-theme");return"dark"===r||"light"!==r&&(this.themeMedia?.matches??!1)}startAudioAnalysis(t){if(this.analyser)return;const e=window.AudioContext??window.webkitAudioContext;if(e)try{const i=new e,s=i.createAnalyser();s.fftSize=512,s.smoothingTimeConstant=.58;const a=i.createMediaStreamSource(t);a.connect(s),this.audioContext=i,this.audioSource=a,this.analyser=s,this.analyserData=new Float32Array(s.fftSize),"suspended"===i.state&&i.resume().catch(()=>{})}catch{this.stopAudioAnalysis()}}stopAudioAnalysis(){try{this.audioSource?.disconnect(),this.analyser?.disconnect()}catch{}this.audioContext&&this.audioContext.close().catch(()=>{}),this.audioContext=null,this.audioSource=null,this.analyser=null,this.analyserData=null}sampleVoiceLevel(t){if(this.analyser&&this.analyserData){this.analyser.getFloatTimeDomainData(this.analyserData);let t=0;for(const e of this.analyserData)t+=e*e;return{level:clamp((Math.sqrt(t/this.analyserData.length)-.01)/.11,0,1),hasInput:!0}}if(this.hasAttribute("voice-level"))return{level:this.voiceLevel,hasInput:!0};return{level:clamp(.16+.12*(Math.sin(2.05*t)+1)+.42*Math.pow(Math.max(0,Math.sin(.86*t+.7)),5),0,1),hasInput:!1}}resize(){this.canvas&&(this.dpr=Math.min(2,window.devicePixelRatio||1),this.cssW=this.clientWidth||72,this.cssH=this.clientHeight||this.cssW,this.canvas.width=Math.round(this.cssW*this.dpr),this.canvas.height=Math.round(this.cssH*this.dpr),prefersReducedMotion()&&this.frame(this.t0+1600))}frame(t){const e=this.ctx;if(!e||!this.cssW)return;let i=e;const s=this.lastFrame?Math.min((t-this.lastFrame)/1e3,.1):1/60;this.lastFrame=t;const a=(t-this.t0)/1e3,c=this.cssW,d=this.cssH,m=c/2,u=d/2,p=Math.min(c,d)/2,g=this.isDarkContext(),b=g?r[this.variant]:o[this.variant],f=n[this.state];this.hover.x+=.08*(this.hover.tx-this.hover.x),this.hover.y+=.08*(this.hover.ty-this.hover.y);const v="listening"===this.state?this.sampleVoiceLevel(a):{level:0},M=v.level>this.displayedVoiceLevel?19:7;this.displayedVoiceLevel+=(v.level-this.displayedVoiceLevel)*(1-Math.exp(-M*s)),"listening"!==this.state&&this.displayedVoiceLevel<.001&&(this.displayedVoiceLevel=0);const S=Math.pow(clamp(this.displayedVoiceLevel,0,1),.72),y="listening"===this.state?this.minScale+(this.maxScale-this.minScale)*S:1,w=f.breatheAmt*("listening"===this.state?.55:1),x=1+Math.sin(a*f.breatheSpeed)*w*(1-this.dotsMix),C="listening"===this.state?S:"thinking"===this.state?1:.55,A="thinking"===this.state?1.38:1,k=.68*p*x*y*("thinking"===this.state&&"dots"!==this.thinkingStyle?1.1:1),R=.99*p;i.setTransform(this.dpr,0,0,this.dpr,0,0),i.clearRect(0,0,c,d),i.globalCompositeOperation="source-over",i.filter="none";const O=this.shapes,L=numberAttribute(this,"morph-interval",2.6,.8,30),z=this.shapeBuffer;let P=0;if(O.length<=1||prefersReducedMotion())z.set(resolveShape(O[0]??"circle"));else{const t=Math.min(1.1,.55*L),e=Math.floor(a/L),i=smoothstep(0,1,(a-e*L-(L-t))/t),s=resolveShape(O[e%O.length]),r=resolveShape(O[(e+1)%O.length]);for(let t=0;t<z.length;t+=1)z[t]=s[t]+(r[t]-s[t])*i;P=(e+i)*h}const T=Math.cos(P),D=Math.sin(P),trace=(t,e,s)=>{const a=l,px=e=>t+(z[e%a*2]*T-z[e%a*2+1]*D)*s,py=t=>e+(z[t%a*2]*D+z[t%a*2+1]*T)*s;i.beginPath(),i.moveTo((px(0)+px(1))/2,(py(0)+py(1))/2);for(let t=1;t<=a;t+=1)i.quadraticCurveTo(px(t),py(t),(px(t)+px(t+1))/2,(py(t)+py(t+1))/2);i.closePath()},E="thinking"===this.state&&"dots"===this.thinkingStyle;prefersReducedMotion()?this.dotsMix=E?1:0:this.dotsMix+=((E?1:0)-this.dotsMix)*(1-Math.exp(-2.6*s)),Math.abs(this.dotsMix-(E?1:0))<.002&&(this.dotsMix=E?1:0);const F=smoothstep(0,1,this.dotsMix),V=.19*k,drawBody=t=>{const e=t;i.globalCompositeOperation="source-over";const s="thinking"===this.state?g?.42:.22:g?.28:.5,r=g?mix(b.core,b.lights[0],s):"thinking"===this.state?mix(b.core,b.lights[0],.35):mix(b.core,[255,255,255],.5),o=mix(b.core,b.lights[1],g?"thinking"===this.state?.35:0:"thinking"===this.state?.28:.12),n=i.createRadialGradient(m,u-.12*e,.1*e,m,u,e);n.addColorStop(0,rgba(r,g?1:.96)),n.addColorStop(.86,rgba(o,g?1:.9)),n.addColorStop(1,rgba(o,g?1:.9)),i.fillStyle=n,trace(m,u,e),i.fill(),i.globalCompositeOperation=g?"lighter":"source-over";const l=b.lights.length;for(let t=0;t<l;t+=1){const s=2.1*t,r=f.turb*(.85+.55*C),o=Math.sin(a*f.speed+s)*f.amp+Math.sin(a*f.speed*1.7+1.4*s)*f.amp*r*.75+Math.sin(a*f.speed*.45+.7*s)*f.amp*.35,n=Math.cos(a*f.speed*.9+1.3*s)*f.amp+Math.cos(a*f.speed*2+s)*f.amp*r*.75+Math.cos(a*f.speed*.55+.9*s)*f.amp*.35,c="thinking"===this.state?.42:.28,d=t/l*h-.5*Math.PI+a*f.speed*c,p=e*("thinking"===this.state?.52:.44),v=m+Math.cos(d)*p+o*e+this.hover.x*e*.12,M=u+Math.sin(d)*p+n*e+this.hover.y*e*.12,S=e*A*(.92+.28*C+.12*Math.sin(a*f.speed*1.3+s)),y="thinking"===this.state?mix(b.lights[t],g?[255,255,255]:b.core,g?.1:.18):b.lights[t],w=(g?.62:.78)*f.lightAlpha*(.85+.28*C)*("thinking"===this.state?1.22:1);i.save(),i.filter=`blur(${e*(g?.15:.18)*("thinking"===this.state?.92:1)}px)`;const x=i.createRadialGradient(v,M,0,v,M,S);x.addColorStop(0,rgba(y,Math.min(1,w))),x.addColorStop(.48,rgba(y,Math.min(1,.5*w))),x.addColorStop(1,rgba(y,0)),i.fillStyle=x,i.beginPath(),i.arc(v,M,S,0,h),i.fill(),i.restore()}i.filter="none",i.globalCompositeOperation="destination-in",i.fillStyle="#000",trace(m,u,e),i.fill(),i.globalCompositeOperation="source-atop";const c=m-.22*e+this.hover.x*e*.1,d=u-.28*e+this.hover.y*e*.1,p=i.createRadialGradient(c,d,0,c,d,.95*e);p.addColorStop(0,`rgba(255,255,255,${g?.14:.26})`),p.addColorStop(1,"rgba(255,255,255,0)"),i.fillStyle=p,i.fillRect(m-e,u-e,2*e,2*e);const v=i.createRadialGradient(m,u,.62*e,m,u,e),M=g?mix(b.rim,[255,255,255],.2):b.rim;v.addColorStop(0,rgba(M,0)),v.addColorStop(1,rgba(M,(g?.26:.2)*(.8+.4*C))),i.fillStyle=v,i.fillRect(m-e,u-e,2*e,2*e),i.globalCompositeOperation="source-over";const S=Math.max(1,.03*e)*("thinking"===this.state?1.2:1),y=b.lights.map(t=>g?mix(t,[255,255,255],.3):mix(t,b.rim,.45)),w=g?.55:.42,x=a*("thinking"===this.state?1.3:.42);let k;"function"==typeof i.createConicGradient?(k=i.createConicGradient(x,m,u),k.addColorStop(0,rgba(y[0],w)),k.addColorStop(.25,rgba(y[1],w)),k.addColorStop(.5,rgba(y[2%y.length],w)),k.addColorStop(.75,rgba(mix(y[1],[255,255,255],.55),w)),k.addColorStop(1,rgba(y[0],w))):(k=i.createLinearGradient(m-e,u-e,m+e,u+e),k.addColorStop(0,rgba(y[0],w)),k.addColorStop(1,rgba(y[1],w))),i.lineJoin="round",i.lineWidth=S,i.strokeStyle=k,trace(m,u,e-S/2),i.stroke();const O=i.createLinearGradient(m-.7*e,u-.9*e,m+.7*e,u+.9*e);O.addColorStop(0,"rgba(255,255,255,0.55)"),O.addColorStop(.35,"rgba(255,255,255,0)"),O.addColorStop(.75,"rgba(255,255,255,0)"),O.addColorStop(1,"rgba(255,255,255,0.22)"),i.lineWidth=Math.max(.7,.45*S),i.strokeStyle=O,trace(m,u,e-1.45*S),i.stroke(),i.globalCompositeOperation="destination-over";const L=i.createRadialGradient(m,u,.7*e,m,u,R),z=(g?.2:.095)*f.glow*(.85+.28*C);L.addColorStop(0,rgba(b.glow,z)),L.addColorStop(1,rgba(b.glow,0)),i.fillStyle=L,i.beginPath(),i.arc(m,u,R,0,h),i.fill(),i.globalCompositeOperation="source-over"},N=mix([(b.lights[0][0]+b.lights[1][0]+b.lights[2][0])/3,(b.lights[0][1]+b.lights[1][1]+b.lights[2][1])/3,(b.lights[0][2]+b.lights[1][2]+b.lights[2][2])/3],b.rim,g?.1:.35),drawDot=(t,e,s,a)=>{if(s<=.2||a<=.01)return;i.save(),i.globalAlpha=a;const r=i.createRadialGradient(t,e,.7*s,t,e,2*s);r.addColorStop(0,rgba(N,g?.18:.1)),r.addColorStop(1,rgba(N,0)),i.fillStyle=r,i.beginPath(),i.arc(t,e,2*s,0,h),i.fill(),i.fillStyle=rgba(N,.92),i.beginPath(),i.arc(t,e,s,0,h),i.fill(),i.lineWidth=Math.max(.8,.08*s),i.strokeStyle=rgba(mix(N,b.rim,.4),.55),i.beginPath(),i.arc(t,e,Math.max(.1,s-i.lineWidth/2),0,h),i.stroke(),i.restore()};if(F<.98){const t=k+(V-k)*smoothstep(0,.85,F);if(F<=.01)drawBody(t);else{const s=this.layer??(this.layer=document.createElement("canvas"));s.width===this.canvas.width&&s.height===this.canvas.height||(s.width=this.canvas.width,s.height=this.canvas.height);const a=s.getContext("2d");a&&(a.setTransform(this.dpr,0,0,this.dpr,0,0),a.clearRect(0,0,c,d),i=a,drawBody(t),i=e,i.save(),i.setTransform(1,0,0,1,0,0),i.globalAlpha=1-smoothstep(.55,.98,F),i.drawImage(s,0,0),i.restore())}}if(F>.01){const t=smoothstep(.3,1,F),e=.5*k*t;for(let i=0;i<3;i+=1){const s=5*a-.9*i,r=Math.pow(Math.max(0,Math.sin(s)),2);drawDot(m+(i-1)*e,u-r*V*1.3*smoothstep(.6,1,F),V*(1===i?1:t),(1===i?smoothstep(.5,.98,F):t)*(.75+.25*r))}}}}customElements.get("db-chat-orb")||customElements.define("db-chat-orb",DbChatOrb);export{DbChatOrb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dbText as t}from"../core/i18n.js";import{createDbIcon as e}from"./db-icons.js";import{sanitizeHtmlFragment as s}from"../core/safe-html.js";import"./db-avatar.js";import"./db-chat-message.js";import"./db-chat-typing.js";import"./db-chat-composer.js";const r="designbase-wc-chat-room";let a=0;function el(t,e,s){const r=document.createElement(t);return e&&(r.className=e),null!=s&&(r.textContent=s),r}class DbChatRoom extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.typing=!1,this.dragDepth=0,this._messages=[],this.handleDragEnter=t=>{this.hasDragFiles(t)&&(t.preventDefault(),this.dragDepth+=1,this.showDropOverlay(!0))},this.handleDragOver=t=>{this.hasDragFiles(t)&&(t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy"))},this.handleDragLeave=()=>{this.dragDepth=Math.max(0,this.dragDepth-1),0===this.dragDepth&&this.showDropOverlay(!1)},this.handleDrop=t=>{if(!this.hasDragFiles(t))return;t.preventDefault(),t.stopPropagation(),this.dragDepth=0,this.showDropOverlay(!1);const e=Array.from(t.dataTransfer?.files||[]);e.length&&this.composer&&(this.composer.addFiles(e,"drop"),this.composer.focusInput())}}static get observedAttributes(){return["peer-name","peer-avatar","peer-initials","peer-status","placeholder","online","show-back"]}set messages(t){this._messages=Array.isArray(t)?t.map(t=>({id:`r${a+=1}`,...t})):[],this.built&&this.renderThread()}get messages(){return this._messages}connectedCallback(){this.built||this.build(),this.sync(),this.addEventListener("dragenter",this.handleDragEnter),this.addEventListener("dragover",this.handleDragOver),this.addEventListener("dragleave",this.handleDragLeave),this.addEventListener("drop",this.handleDrop)}disconnectedCallback(){this.removeEventListener("dragenter",this.handleDragEnter),this.removeEventListener("dragover",this.handleDragOver),this.removeEventListener("dragleave",this.handleDragLeave),this.removeEventListener("drop",this.handleDrop)}attributeChangedCallback(){this.built&&this.sync()}build(){this.built=!0,this.className=r,this.replaceChildren(),this.headerEl=el("div",`${r}__header`),this.scrollEl=el("div",`${r}__scroll`),this.threadEl=el("div",`${r}__thread`),this.scrollEl.append(this.threadEl),this.dockEl=el("div",`${r}__dock`),this.dropOverlay=el("div",`${r}__drop-overlay`),this.dropOverlay.hidden=!0,this.dropOverlay.setAttribute("aria-hidden","true"),this.dropOverlay.append(el("span",`${r}__drop-label`,"파일을 놓으면 첨부됩니다")),this.composer=document.createElement("db-chat-composer"),this.composer.setAttribute("attach",""),this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||"메시지 보내기…"),this.composer.addEventListener("db-send",t=>{t.stopPropagation();const e=t.detail,s=e?.value?.trim()||"",r=e?.attachments||[];if(!s&&!r.length)return;const a=r.map(t=>({name:t.name,kind:t.kind,mime:t.mime,url:t.file?URL.createObjectURL(t.file):t.preview}));this.dispatchEvent(new CustomEvent("db-send",{detail:{value:s,attachments:a},bubbles:!0,composed:!0})),this.addMessage({mine:!0,text:s,time:this.nowLabel(),attachments:a.length?a:void 0})}),this.dockEl.append(this.composer),this.append(this.headerEl,this.scrollEl,this.dockEl,this.dropOverlay),this.renderThread()}sync(){this.renderHeader(),this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||"메시지 보내기…")}renderHeader(){if(this.headerEl.replaceChildren(),this.hasAttribute("show-back")){const s=el("button",`${r}__back`);s.type="button",s.setAttribute("aria-label",t("뒤로")),s.append(e("chevron-left",20)),s.addEventListener("click",()=>this.dispatchEvent(new CustomEvent("db-back",{bubbles:!0,composed:!0}))),this.headerEl.append(s)}const s=document.createElement("db-avatar");s.className=`${r}__avatar`,s.setAttribute("size","m"),s.setAttribute("variant","primary");const a=this.getAttribute("peer-avatar"),i=this.getAttribute("peer-initials");a?s.setAttribute("src",a):i&&s.setAttribute("initials",i),this.hasAttribute("online")&&s.setAttribute("status","online");const n=el("div",`${r}__meta`);n.append(el("span",`${r}__name`,this.getAttribute("peer-name")||"대화"));const h=this.getAttribute("peer-status")||(this.hasAttribute("online")?"온라인":"");h&&n.append(el("span",`${r}__status`,h)),this.headerEl.append(s,n)}renderThread(){this.threadEl.replaceChildren(),this._messages.forEach(t=>this.threadEl.append(this.buildMessage(t))),this.typing&&this.threadEl.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollToEnd())}buildMessage(t){const e=el("db-chat-message");
|
|
1
|
+
import{dbText as t}from"../core/i18n.js";import{createDbIcon as e}from"./db-icons.js";import{sanitizeHtmlFragment as s}from"../core/safe-html.js";import"./db-avatar.js";import"./db-chat-message.js";import"./db-chat-typing.js";import"./db-chat-composer.js";const r="designbase-wc-chat-room";let a=0;function el(t,e,s){const r=document.createElement(t);return e&&(r.className=e),null!=s&&(r.textContent=s),r}class DbChatRoom extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.typing=!1,this.dragDepth=0,this._messages=[],this.handleDragEnter=t=>{this.hasDragFiles(t)&&(t.preventDefault(),this.dragDepth+=1,this.showDropOverlay(!0))},this.handleDragOver=t=>{this.hasDragFiles(t)&&(t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy"))},this.handleDragLeave=()=>{this.dragDepth=Math.max(0,this.dragDepth-1),0===this.dragDepth&&this.showDropOverlay(!1)},this.handleDrop=t=>{if(!this.hasDragFiles(t))return;t.preventDefault(),t.stopPropagation(),this.dragDepth=0,this.showDropOverlay(!1);const e=Array.from(t.dataTransfer?.files||[]);e.length&&this.composer&&(this.composer.addFiles(e,"drop"),this.composer.focusInput())}}static get observedAttributes(){return["peer-name","peer-avatar","peer-initials","peer-status","placeholder","online","show-back"]}set messages(t){this._messages=Array.isArray(t)?t.map(t=>({id:`r${a+=1}`,...t})):[],this.built&&this.renderThread()}get messages(){return this._messages}connectedCallback(){this.built||this.build(),this.sync(),this.addEventListener("dragenter",this.handleDragEnter),this.addEventListener("dragover",this.handleDragOver),this.addEventListener("dragleave",this.handleDragLeave),this.addEventListener("drop",this.handleDrop)}disconnectedCallback(){this.removeEventListener("dragenter",this.handleDragEnter),this.removeEventListener("dragover",this.handleDragOver),this.removeEventListener("dragleave",this.handleDragLeave),this.removeEventListener("drop",this.handleDrop)}attributeChangedCallback(){this.built&&this.sync()}build(){this.built=!0,this.className=r,this.replaceChildren(),this.headerEl=el("div",`${r}__header`),this.scrollEl=el("div",`${r}__scroll`),this.threadEl=el("div",`${r}__thread`),this.scrollEl.append(this.threadEl),this.dockEl=el("div",`${r}__dock`),this.dropOverlay=el("div",`${r}__drop-overlay`),this.dropOverlay.hidden=!0,this.dropOverlay.setAttribute("aria-hidden","true"),this.dropOverlay.append(el("span",`${r}__drop-label`,"파일을 놓으면 첨부됩니다")),this.composer=document.createElement("db-chat-composer"),this.composer.setAttribute("attach",""),this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||"메시지 보내기…"),this.composer.addEventListener("db-send",t=>{t.stopPropagation();const e=t.detail,s=e?.value?.trim()||"",r=e?.attachments||[];if(!s&&!r.length)return;const a=r.map(t=>({name:t.name,kind:t.kind,mime:t.mime,url:t.file?URL.createObjectURL(t.file):t.preview}));this.dispatchEvent(new CustomEvent("db-send",{detail:{value:s,attachments:a},bubbles:!0,composed:!0})),this.addMessage({mine:!0,text:s,time:this.nowLabel(),attachments:a.length?a:void 0})}),this.dockEl.append(this.composer),this.append(this.headerEl,this.scrollEl,this.dockEl,this.dropOverlay),this.renderThread()}sync(){this.renderHeader(),this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||"메시지 보내기…")}renderHeader(){if(this.headerEl.replaceChildren(),this.hasAttribute("show-back")){const s=el("button",`${r}__back`);s.type="button",s.setAttribute("aria-label",t("뒤로")),s.append(e("chevron-left",20)),s.addEventListener("click",()=>this.dispatchEvent(new CustomEvent("db-back",{bubbles:!0,composed:!0}))),this.headerEl.append(s)}const s=document.createElement("db-avatar");s.className=`${r}__avatar`,s.setAttribute("size","m"),s.setAttribute("variant","primary");const a=this.getAttribute("peer-avatar"),i=this.getAttribute("peer-initials");a?s.setAttribute("src",a):i&&s.setAttribute("initials",i),this.hasAttribute("online")&&s.setAttribute("status","online");const n=el("div",`${r}__meta`);n.append(el("span",`${r}__name`,this.getAttribute("peer-name")||"대화"));const h=this.getAttribute("peer-status")||(this.hasAttribute("online")?"온라인":"");h&&n.append(el("span",`${r}__status`,h)),this.headerEl.append(s,n)}renderThread(){this.threadEl.replaceChildren(),this._messages.forEach(t=>this.threadEl.append(this.buildMessage(t))),this.typing&&this.threadEl.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollToEnd())}buildMessage(t){const e=el("db-chat-message");e.dataset.id=t.id||"",e.setAttribute("role",t.mine?"user":"assistant"),!t.mine&&t.name&&e.setAttribute("name",t.name),t.time&&e.setAttribute("timestamp",t.time);const r=t.avatarSrc||(t.mine?null:this.getAttribute("peer-avatar"));return r&&e.setAttribute("avatar-src",r),t.avatarInitials&&e.setAttribute("avatar-initials",t.avatarInitials),null!=t.html?e.replaceChildren(s(t.html)):e.textContent=t.text||"",t.attachments?.length&&(e.attachments=t.attachments),e}buildTyping(){const t=el("db-chat-typing");t.setAttribute("name",this.getAttribute("peer-name")||"");const e=this.getAttribute("peer-avatar");return e&&t.setAttribute("avatar-src",e),t.setAttribute("avatar-initials",this.getAttribute("peer-initials")||""),t}scrollToEnd(){this.scrollEl.scrollTop=this.scrollEl.scrollHeight}nowLabel(){const t=new Date,e=t.getHours();return`${e<12?"오전":"오후"} ${e%12==0?12:e%12}:${t.getMinutes().toString().padStart(2,"0")}`}hasDragFiles(t){return Array.from(t.dataTransfer?.types||[]).includes("Files")}showDropOverlay(t){this.dropOverlay.hidden=!t,this.classList.toggle(`${r}--drop-active`,t)}addMessage(t){const e={id:`r${a+=1}`,...t};this._messages.push(e);const s=this.threadEl.querySelector("db-chat-typing"),r=this.buildMessage(e);return s?this.threadEl.insertBefore(r,s):this.threadEl.append(r),requestAnimationFrame(()=>this.scrollToEnd()),e.id}setMessages(t){this.messages=t}setTyping(t){if(this.typing=Boolean(t),!this.built)return;const e=this.threadEl.querySelector("db-chat-typing");this.typing&&!e?(this.threadEl.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollToEnd())):!this.typing&&e&&e.remove()}focusInput(){this.composer?.focusInput()}getComposer(){return this.composer}}customElements.get("db-chat-room")||customElements.define("db-chat-room",DbChatRoom);export{DbChatRoom};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dbText as t}from"../core/i18n.js";import{createDbIcon as e,isDbIconName as i}from"./db-icons.js";import"./db-chat-orb.js";import"./db-badge.js";import"./db-chat-welcome.js";import"./db-chat-composer.js";import"./db-chat-suggestions.js";import"./db-chat-message.js";import"./db-chat-typing.js";import"./db-avatar.js";import{CHAT_WIDGET_VIEWS as s,CHAT_WIDGET_POSITIONS as n}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const r=[{id:"c1",name:"디지털 상담원",avatarInitials:"상",preview:"무엇을 도와드릴까요? 24시간 대기중이에요.",time:"방금",unread:1,online:!0},{id:"c2",name:"결제·환불 지원",avatarInitials:"결",preview:"환불 요청이 정상 접수되었습니다.",time:"어제"},{id:"c3",name:"온보딩 도우미",avatarInitials:"온",preview:"설치가 완료되면 알려드릴게요.",time:"3일 전"}],a={chat:'<path d="M4 5.5A1.5 1.5 0 0 1 5.5 4h13A1.5 1.5 0 0 1 20 5.5v9a1.5 1.5 0 0 1-1.5 1.5H9l-4 3.5V16H5.5A1.5 1.5 0 0 1 4 14.5z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/>',home:'<path d="M4 10.5 12 4l8 6.5M6 9.5V19a1 1 0 0 0 1 1h3.5v-4.5a1.5 1.5 0 0 1 3 0V20H17a1 1 0 0 0 1-1V9.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>',help:'<circle cx="12" cy="12" r="8.25" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M9.6 9.4a2.5 2.5 0 0 1 4.9.6c0 1.7-2.5 2-2.5 3.6" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="12" cy="16.6" r="0.95" fill="currentColor"/>',"close-x":'<path d="m6.5 6.5 11 11m0-11-11 11" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>'};function widgetIcon(t,e=20){const i=document.createElementNS("http://www.w3.org/2000/svg","svg");return i.setAttribute("viewBox","0 0 24 24"),i.setAttribute("width",String(e)),i.setAttribute("height",String(e)),i.setAttribute("fill","none"),i.setAttribute("aria-hidden","true"),i.setAttribute("focusable","false"),i.innerHTML=a[t],i}function el(t,e,i){const s=document.createElement(t);return e&&(s.className=e),null!=i&&(s.textContent=i),s}function parseJson(t,e){if(!t)return e;try{const i=JSON.parse(t);return Array.isArray(i)?i:e}catch{return e}}const o="designbase-wc-chat";class DbChatWidget extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.typing=!1,this.helpQuery="",this._activeArticle=null,this._messages=[{id:"m1",role:"assistant",name:"상담원",text:t("안녕하세요. 무엇을 도와드릴까요? 제품과 정책을 24시간 정확하게 안내해 드릴게요."),time:"오후 12:31"},{id:"m2",role:"assistant",name:"상담원",text:t("아래에서 원하시는 도움을 선택하시거나, 자유롭게 메시지를 입력해 주세요."),time:"오후 12:31",actions:"like,dislike"}],this._conversations=r,this._menuItems=[{id:"products",title:t("제품·정책 알아보기"),description:t("AI가 정확하게 안내해 드려요"),icon:"sparkle",action:"help"},{id:"services",title:t("상담원과 대화하기"),description:t("지금 바로 대화를 시작하세요"),icon:"mail",action:"conversation"}],this._quickReplies=[{id:"products",title:t("제품·정책 알아보기")},{id:"services",title:t("서비스 상담하기")},{id:"refund",title:t("환불·결제 문의")},{id:"account",title:t("계정·로그인 도움")}],this._articles=[{id:"a1",title:t("시작하기 — 5분 만에 설정 완료"),description:t("계정 생성부터 첫 배포까지"),icon:"sparkle",body:"계정을 만들고 첫 프로젝트를 배포하기까지 약 5분이면 충분합니다.\n\n1. 회원가입 후 이메일 인증\n2. 워크스페이스 생성\n3. 템플릿 선택 또는 빈 프로젝트 시작\n4. 배포 버튼을 눌러 미리보기 URL 확인"},{id:"a2",title:t("요금제와 결제 안내"),description:t("플랜 비교 · 청구 주기"),icon:"star",body:"Free · Pro · Team 플랜을 제공합니다. 청구는 월간 또는 연간으로 선택할 수 있으며, 연간 결제는 할인율이 적용됩니다.\n\n결제 수단 변경과 영수증 다운로드는 설정 > 결제에서 가능합니다."},{id:"a3",title:t("팀원 초대와 권한 관리"),description:t("역할별 접근 권한 설정"),icon:"user",body:"Owner · Admin · Member · Viewer 역할을 지원합니다.\n\n설정 > 팀원에서 이메일로 초대하고, 역할은 언제든 변경할 수 있습니다."},{id:"a4",title:t("데이터 내보내기·백업"),description:t("안전하게 보관하는 방법"),icon:"download",body:"프로젝트 데이터는 JSON · CSV로 내보낼 수 있습니다. 자동 백업은 Pro 이상에서 매일 실행되며, 설정 > 데이터에서 수동 백업도 가능합니다."}],this.composer=null,this.threadEl=null,this.handleDocumentKeyDown=t=>{"Escape"===t.key&&this.open&&(t.preventDefault(),this.close(),this.launcher?.focus())},this.panelDragDepth=0,this.handlePanelDragEnter=t=>{"conversation"===this.view&&this.composer&&Array.from(t.dataTransfer?.types||[]).includes("Files")&&(t.preventDefault(),this.panelDragDepth+=1,this.bodyEl.classList.add(`${o}__body--drop-active`))},this.handlePanelDragOver=t=>{"conversation"===this.view&&this.composer&&Array.from(t.dataTransfer?.types||[]).includes("Files")&&(t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy"))},this.handlePanelDragLeave=()=>{this.panelDragDepth=Math.max(0,this.panelDragDepth-1),0===this.panelDragDepth&&this.bodyEl.classList.remove(`${o}__body--drop-active`)},this.handlePanelDrop=t=>{if("conversation"!==this.view||!this.composer)return;if(!Array.from(t.dataTransfer?.types||[]).includes("Files"))return;t.preventDefault(),t.stopPropagation(),this.panelDragDepth=0,this.bodyEl.classList.remove(`${o}__body--drop-active`);const e=Array.from(t.dataTransfer?.files||[]);e.length&&(this.composer.addFiles(e,"drop"),this.composer.focusInput())}}static get observedAttributes(){return["open","view","position","agent-name","agent-avatar","agent-status","greeting","title","subtitle","placeholder","unread","accent","orb-variant","launcher-label"]}get open(){return this.hasAttribute("open")}set open(t){this.toggleAttribute("open",Boolean(t))}get view(){const t=this.getAttribute("view");return s.includes(t)?t:"home"}set view(t){this.setAttribute("view",t)}get position(){const t=this.getAttribute("position");return n.includes(t)?t:"bottom-right"}set position(t){this.setAttribute("position",t)}get unread(){return Number(this.getAttribute("unread")||0)}set unread(t){this.setAttribute("unread",String(t))}set messages(t){this._messages=Array.isArray(t)?t:[],this.built&&"conversation"===this.view&&this.renderBody()}get messages(){return this._messages}set conversations(t){this._conversations=Array.isArray(t)?t:[],this.built&&this.renderBody()}get conversations(){return this._conversations}set menuItems(t){this._menuItems=Array.isArray(t)?t:[],this.built&&"home"===this.view&&this.renderBody()}get menuItems(){return this._menuItems}set quickReplies(t){this._quickReplies=Array.isArray(t)?t:[],this.built&&"conversation"===this.view&&this.renderBody()}get quickReplies(){return this._quickReplies}set articles(t){this._articles=Array.isArray(t)?t:[],!this.built||"help"!==this.view&&"article"!==this.view||this.renderBody()}get articles(){return this._articles}connectedCallback(){this.hydrateFromAttributes(),this.build(),this.sync(),document.addEventListener("keydown",this.handleDocumentKeyDown)}disconnectedCallback(){document.removeEventListener("keydown",this.handleDocumentKeyDown)}attributeChangedCallback(t,e,i){if(this.built&&e!==i){if("open"!==t)return"view"===t?(this.sync(),void this.dispatch("db-view-change",{view:this.view})):void this.sync();this.syncOpenState()}}hydrateFromAttributes(){this._messages=parseJson(this.getAttribute("messages"),this._messages),this._conversations=parseJson(this.getAttribute("conversations"),this._conversations),this._menuItems=parseJson(this.getAttribute("menu-items"),this._menuItems),this._quickReplies=parseJson(this.getAttribute("quick-replies"),this._quickReplies),this._articles=parseJson(this.getAttribute("articles"),this._articles)}build(){if(this.built)return;this.built=!0,this.classList.add(o),this.replaceChildren(),this.launcher=el("button",`${o}__launcher`),this.launcher.type="button",this.launcher.setAttribute("aria-haspopup","dialog");const t=el("span",`${o}__launcher-icon ${o}__launcher-icon--open`),e=el("db-chat-orb");e.setAttribute("size","s"),e.setAttribute("variant",this.getAttribute("orb-variant")||"violet"),e.setAttribute("state","idle"),t.append(e);const i=el("span",`${o}__launcher-icon ${o}__launcher-icon--close`);i.append(widgetIcon("close-x",24)),this.launcherBadge=document.createElement("db-badge"),this.launcherBadge.className=`${o}__launcher-badge`,this.launcherBadge.setAttribute("type","number"),this.launcherBadge.setAttribute("variant","danger"),this.launcherBadge.setAttribute("size","s"),this.launcherBadge.setAttribute("max-count","99"),this.launcher.append(t,i),this.launcher.addEventListener("click",()=>this.toggle()),this.panel=el("div",`${o}__panel`),this.panel.setAttribute("role","dialog"),this.panel.setAttribute("aria-modal","false"),this.headerEl=el("div",`${o}__header`),this.bodyEl=el("div",`${o}__body`),this.footerEl=el("div",`${o}__footer`),this.navEl=el("div",`${o}__nav`),this.panel.append(this.headerEl,this.bodyEl,this.footerEl,this.navEl),this.append(this.panel,this.launcher,this.launcherBadge),this.bodyEl.addEventListener("dragenter",this.handlePanelDragEnter),this.bodyEl.addEventListener("dragover",this.handlePanelDragOver),this.bodyEl.addEventListener("dragleave",this.handlePanelDragLeave),this.bodyEl.addEventListener("drop",this.handlePanelDrop)}sync(){n.forEach(t=>this.classList.toggle(`${o}--${t}`,t===this.position)),s.forEach(t=>this.classList.toggle(`${o}--view-${t}`,t===this.view));const t=this.getAttribute("accent");t?this.style.setProperty("--dbchat-accent",t):this.style.removeProperty("--dbchat-accent"),this.renderBadge(),this.renderHeader(),this.renderBody(),this.renderFooter(),this.renderNav(),this.syncOpenState()}syncOpenState(){const t=this.open;this.classList.toggle(`${o}--open`,t),this.launcher.setAttribute("aria-expanded",String(t)),this.panel.setAttribute("aria-hidden",String(!t)),t?this.panel.removeAttribute("inert"):this.panel.setAttribute("inert",""),this.launcher.setAttribute("aria-label",this.getAttribute("launcher-label")||(t?"상담창 닫기":"상담 시작하기")),t&&"conversation"===this.view&&requestAnimationFrame(()=>this.composer?.focusInput())}renderBadge(){const t=this.unread;t>0?this.launcherBadge.setAttribute("count",String(t)):this.launcherBadge.removeAttribute("count"),this.launcherBadge.hidden=t<=0}renderHeader(){this.headerEl.replaceChildren();const t=this.view,i=this.getAttribute("agent-name")||"디지털 상담원",s=el("div",`${o}__header-bar`);if("conversation"===t){const t=this.iconButton(e("chevron-left",20),"뒤로",()=>this.setView("messages")),n=el("div",`${o}__header-identity`),r=el("db-chat-orb");r.className=`${o}__header-orb`,r.setAttribute("size","xs"),r.setAttribute("variant",this.getAttribute("orb-variant")||"violet");const a=el("div",`${o}__header-meta`);a.append(el("span",`${o}__header-name`,i),el("span",`${o}__header-status`,this.getAttribute("agent-status")||"보통 몇 분 내 응답")),n.append(r,a),s.append(t,n,this.iconButton(widgetIcon("close-x",20),"닫기",()=>this.close()))}else if("article"===t){const t=this.iconButton(e("chevron-left",20),"뒤로",()=>{this._activeArticle=null,this.setView("help")});s.append(t,el("span",`${o}__header-title`,this._activeArticle?.title||"도움말"),this.iconButton(widgetIcon("close-x",20),"닫기",()=>this.close()))}else{const e="home"===t?"홈":"messages"===t?"메시지":"도움말";s.append(el("span",`${o}__header-title`,e),this.iconButton(widgetIcon("close-x",20),"닫기",()=>this.close()))}this.headerEl.append(s)}iconButton(t,e,i){const s=el("button",`${o}__icon-btn`);return s.type="button",s.setAttribute("aria-label",e),s.append(t),s.addEventListener("click",i),s}renderBody(){if(this.built)switch(this.threadEl=null,this.bodyEl.replaceChildren(),this.bodyEl.classList.toggle(`${o}__body--thread`,"conversation"===this.view),this.bodyEl.classList.toggle(`${o}__body--article`,"article"===this.view),this.view){case"home":this.renderHome();break;case"messages":this.renderMessages();break;case"conversation":this.renderConversation();break;case"help":this.renderHelp();break;case"article":this.renderArticle()}}renderHome(){const t=el("db-chat-welcome",`${o}__welcome`);t.setAttribute("greeting",this.getAttribute("greeting")||"안녕하세요"),t.setAttribute("title",this.getAttribute("title")||"무엇을 도와드릴까요?"),t.setAttribute("highlight",this.getAttribute("highlight")||this.getAttribute("title")||"도와드릴까요"),t.setAttribute("subtitle",this.getAttribute("subtitle")||"제품·정책을 24시간 정확하게 안내해 드릴게요."),t.setAttribute("orb-variant",this.getAttribute("orb-variant")||"violet"),t.setAttribute("align","left"),t.setAttribute("orb-size","m"),this.bodyEl.append(t);const i=this._conversations[0];if(i){const t=el("button",`${o}__recent`);t.type="button";const s=el("db-avatar",`${o}__recent-avatar`);s.setAttribute("size","s"),s.setAttribute("variant","primary"),i.avatarSrc?s.setAttribute("src",i.avatarSrc):s.setAttribute("initials",i.avatarInitials||i.name.charAt(0)||"상"),i.online&&s.setAttribute("status","online"),i.unread&&(s.setAttribute("badge",String(i.unread)),s.setAttribute("type","number"),s.setAttribute("badge-variant","primary"),s.setAttribute("badge-max-count","99"));const n=el("div",`${o}__recent-body`);n.append(el("span",`${o}__recent-title`,"최근 대화 이어가기"),el("span",`${o}__recent-preview`,i.preview||"")),t.append(s,n,e("chevron-right",18)),t.addEventListener("click",()=>{this.dispatch("db-conversation-select",{id:i.id}),this.setView("conversation")}),this.bodyEl.append(t)}else this.bodyEl.append(this.buildStartChatButton("대화 시작하기","primary"));if(this._menuItems.length){const t=el("db-chat-suggestions",`${o}__menu`);t.setAttribute("layout","card"),t.setAttribute("columns","1"),t.items=this._menuItems,t.addEventListener("db-select",t=>{t.stopPropagation();const e=t.detail,i=this._menuItems.find(t=>(t.id??t.title)===e.id),s=i?.action??"none";this.dispatch("db-menu-select",{...e,action:s}),"conversation"===s?this.startConversation():"help"===s&&this.setView("help")}),this.bodyEl.append(t)}}renderMessages(){if(!this._conversations.length)return void this.bodyEl.append(this.buildEmpty("아직 대화가 없어요","궁금한 점이 있으면 새 대화를 시작해 보세요.",{ctaLabel:t("대화 시작하기"),onCta:()=>this.startConversation()}));const e=el("div",`${o}__conv-list`);this._conversations.forEach(t=>{const i=el("button",`${o}__conv`);i.type="button";const s=el("db-avatar",`${o}__conv-avatar`);s.setAttribute("size","m"),s.setAttribute("variant","primary"),t.avatarSrc?s.setAttribute("src",t.avatarSrc):s.setAttribute("initials",t.avatarInitials||t.name.charAt(0)||"?"),t.online&&s.setAttribute("status","online");const n=el("div",`${o}__conv-body`),r=el("div",`${o}__conv-top`);if(r.append(el("span",`${o}__conv-name`,t.name)),t.time&&r.append(el("span",`${o}__conv-time`,t.time)),n.append(r,el("span",`${o}__conv-preview`,t.preview||"")),i.append(s,n),t.unread){const e=el("db-badge",`${o}__conv-unread`);e.setAttribute("type","number"),e.setAttribute("variant","primary"),e.setAttribute("size","s"),e.setAttribute("max-count","99"),e.setAttribute("count",String(t.unread)),i.append(e)}i.addEventListener("click",()=>{this.dispatch("db-conversation-select",{id:t.id}),this.setView("conversation")}),e.append(i)}),this.bodyEl.append(e)}renderConversation(){const t=el("div",`${o}__thread`);if(this._messages.forEach(e=>t.append(this.buildMessage(e))),this.threadEl=t,this.bodyEl.append(t),this.typing&&t.append(this.buildTyping()),this._quickReplies.length){const t=el("db-chat-suggestions");t.setAttribute("data-dbchat","quick"),t.setAttribute("layout","chip"),t.items=this._quickReplies,t.addEventListener("db-select",t=>{t.stopPropagation();const e=t.detail;this.dispatch("db-quick-reply",e),this.addMessage({role:"user",text:e.title,time:this.nowLabel()})}),this.bodyEl.append(t)}requestAnimationFrame(()=>this.scrollThreadToEnd())}renderHelp(){const i=el("div",`${o}__help-search`),s=el("span",`${o}__help-search-icon`);s.append(e("search",18));const n=el("input",`${o}__help-input`);n.type="search",n.placeholder=t("도움말 검색…"),n.value=this.helpQuery,n.addEventListener("input",()=>{this.helpQuery=n.value,this.renderHelpResults()}),i.append(s,n),this.bodyEl.append(i);const r=el("div",`${o}__help-results`);this.bodyEl.append(r),this.renderHelpResults()}renderHelpResults(){const t=this.bodyEl.querySelector(`.${o}__help-results`);if(!t)return;t.replaceChildren();const e=this.helpQuery.trim().toLowerCase(),i=e?this._articles.filter(t=>t.title.toLowerCase().includes(e)||(t.description||"").toLowerCase().includes(e)||(t.body||"").toLowerCase().includes(e)):this._articles;if(!i.length)return void t.append(this.buildEmpty(e?"검색 결과가 없어요":"문서가 없어요",e?"다른 검색어로 다시 시도해 보세요.":"도움말 문서가 준비되면 여기에 표시됩니다."));const s=el("db-chat-suggestions",`${o}__articles`);s.setAttribute("layout","card"),s.setAttribute("columns","1"),s.items=i,s.addEventListener("db-select",t=>{t.stopPropagation();const e=t.detail,s=this._articles.find(t=>(t.id??t.title)===e.id)||i.find(t=>(t.id??t.title)===e.id);this.dispatch("db-article-select",{id:e.id,title:e.title}),s&&this.openArticle(s)}),t.append(s)}renderArticle(){const t=this._activeArticle;if(!t)return void this.setView("help");const s=el("article",`${o}__article`);if(t.icon&&i(t.icon)){const i=el("div",`${o}__article-icon`);i.append(e(t.icon,22)),s.append(i)}s.append(el("h2",`${o}__article-title`,t.title)),t.description&&s.append(el("p",`${o}__article-desc`,t.description));const n=el("div",`${o}__article-body`);(t.body||t.description||"").split(/\n{2,}/).forEach(t=>{const e=el("p",void 0,t.trim());e.textContent&&n.append(e)}),s.append(n);const r=this.buildStartChatButton("이 내용으로 문의하기","secondary");s.append(r),this.bodyEl.append(s)}openArticle(t){this._activeArticle=t,this.setView("article")}startConversation(){this.dispatch("db-start-chat"),this.setView("conversation")}buildMessage(t){const e="user"===t.role?"user":"assistant",i=el("db-chat-message");return i.setAttribute("role",e),t.name&&i.setAttribute("name",t.name),t.time&&i.setAttribute("timestamp",t.time),t.avatarSrc&&i.setAttribute("avatar-src",t.avatarSrc),t.avatarInitials&&i.setAttribute("avatar-initials",t.avatarInitials),t.actions&&i.setAttribute("actions",t.actions),i.textContent=t.text||"",t.attachments?.length&&(i.attachments=t.attachments),i.addEventListener("db-action",t=>{t.stopPropagation(),this.dispatch("db-message-action",t.detail)}),i}buildTyping(){const t=el("db-chat-typing",`${o}__typing`);return t.setAttribute("name",this.getAttribute("agent-name")||"상담원"),t.setAttribute("label","입력 중"),t}buildEmpty(t,e,i){const s=el("div",`${o}__empty`),n=el("db-chat-orb",`${o}__empty-orb`);return n.setAttribute("size","m"),n.setAttribute("variant",this.getAttribute("orb-variant")||"violet"),s.append(n,el("p",`${o}__empty-title`,t),el("p",`${o}__empty-desc`,e)),i?.ctaLabel&&i.onCta&&s.append(this.buildStartChatButton(i.ctaLabel,"primary",i.onCta)),s}buildStartChatButton(t,e="primary",i){const s=el("button",`${o}__cta ${o}__cta--${e}`);return s.type="button",s.append(widgetIcon("chat",18),el("span",void 0,t)),s.addEventListener("click",()=>i?i():this.startConversation()),s}renderFooter(){const t="conversation"===this.view;if(this.footerEl.hidden=!t,!t)return this.composer=null,void this.footerEl.replaceChildren();if(!this.composer){this.footerEl.replaceChildren();const t=el("db-chat-composer",`${o}__composer`);t.setAttribute("placeholder",this.getAttribute("placeholder")||"메시지를 입력하세요…"),t.setAttribute("attach",""),t.setAttribute("voice",""),t.addEventListener("db-send",t=>{t.stopPropagation();const e=t.detail,i=e?.value?.trim()||"",s=e?.attachments||[];if(!i&&!s.length)return;const n=s.map(t=>({name:t.name,kind:t.kind,mime:t.mime,url:t.file?URL.createObjectURL(t.file):t.preview}));this.dispatch("db-send",{text:i,attachments:n}),this.addMessage({role:"user",text:i,time:this.nowLabel(),attachments:n.length?n:void 0})}),t.addEventListener("db-stop",t=>{t.stopPropagation(),this.dispatch("db-stop")}),t.addEventListener("db-attach",t=>{t.stopPropagation(),this.dispatch("db-attach")}),t.addEventListener("db-voice",t=>{t.stopPropagation(),this.dispatch("db-voice")}),this.composer=t,this.footerEl.append(t)}}renderNav(){this.navEl.replaceChildren();const e="conversation"!==this.view&&"article"!==this.view;if(this.navEl.hidden=!e,!e)return;[{view:"home",icon:"home",label:t("홈")},{view:"messages",icon:"chat",label:t("메시지")},{view:"help",icon:"help",label:t("도움말")}].forEach(t=>{const e=el("button",`${o}__tab`);e.type="button";const i=this.view===t.view;if(e.classList.toggle(`${o}__tab--active`,i),e.setAttribute("aria-current",String(i)),e.append(widgetIcon(t.icon,22),el("span",`${o}__tab-label`,t.label)),"messages"===t.view&&this.unread>0){const t=el("db-badge",`${o}__tab-badge`);t.setAttribute("type","dot"),t.setAttribute("variant","danger"),t.setAttribute("size","s"),e.append(t)}e.addEventListener("click",()=>{"help"===t.view&&(this.helpQuery=""),this.setView(t.view)}),this.navEl.append(e)})}scrollThreadToEnd(){this.bodyEl.scrollTop=this.bodyEl.scrollHeight}nowLabel(){const t=new Date,e=t.getHours();return`${e<12?"오전":"오후"} ${e%12==0?12:e%12}:${t.getMinutes().toString().padStart(2,"0")}`}dispatch(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}openWidget(){this.open||(this.open=!0,this.dispatch("db-open"),this.dispatch("db-toggle",{open:!0}))}close(){this.open&&(this.open=!1,this.dispatch("db-close"),this.dispatch("db-toggle",{open:!1}))}toggle(){this.open?this.close():this.openWidget()}setView(t){this.view!==t&&(this.view=t)}addMessage(t){if(this._messages=[...this._messages,{id:`m${Date.now()}`,...t}],"conversation"===this.view)if(this.built&&this.threadEl){const e=this.threadEl.querySelector("db-chat-typing");this.threadEl.insertBefore(this.buildMessage(t),e),requestAnimationFrame(()=>this.scrollThreadToEnd())}else this.built&&this.renderBody();else this.view="conversation"}setTyping(t){if(this.typing=Boolean(t),!this.built||"conversation"!==this.view||!this.threadEl)return;const e=this.threadEl.querySelector("db-chat-typing");this.typing&&!e?(this.threadEl.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollThreadToEnd())):!this.typing&&e&&e.remove()}setStreaming(t){this.composer?.setStreaming(Boolean(t))}setMessages(t){this.messages=t}}customElements.get("db-chat-widget")||customElements.define("db-chat-widget",DbChatWidget);export{DbChatWidget};
|
|
1
|
+
import{dbText as t}from"../core/i18n.js";import{createDbIcon as e,isDbIconName as i}from"./db-icons.js";import"./db-chat-orb.js";import"./db-badge.js";import"./db-chat-welcome.js";import"./db-chat-composer.js";import"./db-chat-suggestions.js";import"./db-chat-message.js";import"./db-chat-typing.js";import"./db-avatar.js";import{CHAT_WIDGET_VIEWS as s,CHAT_WIDGET_POSITIONS as a}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const n=[{id:"c1",name:"디지털 상담원",avatarInitials:"상",preview:"무엇을 도와드릴까요? 24시간 대기중이에요.",time:"방금",unread:1,online:!0},{id:"c2",name:"결제·환불 지원",avatarInitials:"결",preview:"환불 요청이 정상 접수되었습니다.",time:"어제"},{id:"c3",name:"온보딩 도우미",avatarInitials:"온",preview:"설치가 완료되면 알려드릴게요.",time:"3일 전"}],r={chat:'<path d="M4 5.5A1.5 1.5 0 0 1 5.5 4h13A1.5 1.5 0 0 1 20 5.5v9a1.5 1.5 0 0 1-1.5 1.5H9l-4 3.5V16H5.5A1.5 1.5 0 0 1 4 14.5z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/>',home:'<path d="M4 10.5 12 4l8 6.5M6 9.5V19a1 1 0 0 0 1 1h3.5v-4.5a1.5 1.5 0 0 1 3 0V20H17a1 1 0 0 0 1-1V9.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>',help:'<circle cx="12" cy="12" r="8.25" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M9.6 9.4a2.5 2.5 0 0 1 4.9.6c0 1.7-2.5 2-2.5 3.6" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="12" cy="16.6" r="0.95" fill="currentColor"/>',"close-x":'<path d="m6.5 6.5 11 11m0-11-11 11" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>'};function widgetIcon(t,e=20){const i=document.createElementNS("http://www.w3.org/2000/svg","svg");return i.setAttribute("viewBox","0 0 24 24"),i.setAttribute("width",String(e)),i.setAttribute("height",String(e)),i.setAttribute("fill","none"),i.setAttribute("aria-hidden","true"),i.setAttribute("focusable","false"),i.innerHTML=r[t],i}function el(t,e,i){const s=document.createElement(t);return e&&(s.className=e),null!=i&&(s.textContent=i),s}function parseJson(t,e){if(!t)return e;try{const i=JSON.parse(t);return Array.isArray(i)?i:e}catch{return e}}const o="designbase-wc-chat";class DbChatWidget extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.typing=!1,this.helpQuery="",this._activeArticle=null,this._messages=[{id:"m1",role:"assistant",name:"상담원",text:t("안녕하세요. 무엇을 도와드릴까요? 제품과 정책을 24시간 정확하게 안내해 드릴게요."),time:"오후 12:31"},{id:"m2",role:"assistant",name:"상담원",text:t("아래에서 원하시는 도움을 선택하시거나, 자유롭게 메시지를 입력해 주세요."),time:"오후 12:31",actions:"like,dislike"}],this._conversations=n,this._menuItems=[{id:"products",title:t("제품·정책 알아보기"),description:t("AI가 정확하게 안내해 드려요"),icon:"sparkle",action:"help"},{id:"services",title:t("상담원과 대화하기"),description:t("지금 바로 대화를 시작하세요"),icon:"mail",action:"conversation"}],this._quickReplies=[{id:"products",title:t("제품·정책 알아보기")},{id:"services",title:t("서비스 상담하기")},{id:"refund",title:t("환불·결제 문의")},{id:"account",title:t("계정·로그인 도움")}],this._articles=[{id:"a1",title:t("시작하기 — 5분 만에 설정 완료"),description:t("계정 생성부터 첫 배포까지"),icon:"sparkle",body:"계정을 만들고 첫 프로젝트를 배포하기까지 약 5분이면 충분합니다.\n\n1. 회원가입 후 이메일 인증\n2. 워크스페이스 생성\n3. 템플릿 선택 또는 빈 프로젝트 시작\n4. 배포 버튼을 눌러 미리보기 URL 확인"},{id:"a2",title:t("요금제와 결제 안내"),description:t("플랜 비교 · 청구 주기"),icon:"star",body:"Free · Pro · Team 플랜을 제공합니다. 청구는 월간 또는 연간으로 선택할 수 있으며, 연간 결제는 할인율이 적용됩니다.\n\n결제 수단 변경과 영수증 다운로드는 설정 > 결제에서 가능합니다."},{id:"a3",title:t("팀원 초대와 권한 관리"),description:t("역할별 접근 권한 설정"),icon:"user",body:"Owner · Admin · Member · Viewer 역할을 지원합니다.\n\n설정 > 팀원에서 이메일로 초대하고, 역할은 언제든 변경할 수 있습니다."},{id:"a4",title:t("데이터 내보내기·백업"),description:t("안전하게 보관하는 방법"),icon:"download",body:"프로젝트 데이터는 JSON · CSV로 내보낼 수 있습니다. 자동 백업은 Pro 이상에서 매일 실행되며, 설정 > 데이터에서 수동 백업도 가능합니다."}],this.composer=null,this.threadEl=null,this.handleDocumentKeyDown=t=>{"Escape"===t.key&&this.open&&(t.preventDefault(),this.close(),this.launcher?.focus())},this.panelDragDepth=0,this.handlePanelDragEnter=t=>{"conversation"===this.view&&this.composer&&Array.from(t.dataTransfer?.types||[]).includes("Files")&&(t.preventDefault(),this.panelDragDepth+=1,this.bodyEl.classList.add(`${o}__body--drop-active`))},this.handlePanelDragOver=t=>{"conversation"===this.view&&this.composer&&Array.from(t.dataTransfer?.types||[]).includes("Files")&&(t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy"))},this.handlePanelDragLeave=()=>{this.panelDragDepth=Math.max(0,this.panelDragDepth-1),0===this.panelDragDepth&&this.bodyEl.classList.remove(`${o}__body--drop-active`)},this.handlePanelDrop=t=>{if("conversation"!==this.view||!this.composer)return;if(!Array.from(t.dataTransfer?.types||[]).includes("Files"))return;t.preventDefault(),t.stopPropagation(),this.panelDragDepth=0,this.bodyEl.classList.remove(`${o}__body--drop-active`);const e=Array.from(t.dataTransfer?.files||[]);e.length&&(this.composer.addFiles(e,"drop"),this.composer.focusInput())}}static get observedAttributes(){return["open","view","position","agent-name","agent-avatar","agent-status","greeting","title","subtitle","placeholder","unread","accent","orb-variant","launcher-label"]}get open(){return this.hasAttribute("open")}set open(t){this.toggleAttribute("open",Boolean(t))}get view(){const t=this.getAttribute("view");return s.includes(t)?t:"home"}set view(t){this.setAttribute("view",t)}get position(){const t=this.getAttribute("position");return a.includes(t)?t:"bottom-right"}set position(t){this.setAttribute("position",t)}get unread(){return Number(this.getAttribute("unread")||0)}set unread(t){this.setAttribute("unread",String(t))}set messages(t){this._messages=Array.isArray(t)?t:[],this.built&&"conversation"===this.view&&this.renderBody()}get messages(){return this._messages}set conversations(t){this._conversations=Array.isArray(t)?t:[],this.built&&this.renderBody()}get conversations(){return this._conversations}set menuItems(t){this._menuItems=Array.isArray(t)?t:[],this.built&&"home"===this.view&&this.renderBody()}get menuItems(){return this._menuItems}set quickReplies(t){this._quickReplies=Array.isArray(t)?t:[],this.built&&"conversation"===this.view&&this.renderBody()}get quickReplies(){return this._quickReplies}set articles(t){this._articles=Array.isArray(t)?t:[],!this.built||"help"!==this.view&&"article"!==this.view||this.renderBody()}get articles(){return this._articles}connectedCallback(){this.hydrateFromAttributes(),this.build(),this.sync(),document.addEventListener("keydown",this.handleDocumentKeyDown)}disconnectedCallback(){document.removeEventListener("keydown",this.handleDocumentKeyDown)}attributeChangedCallback(t,e,i){if(this.built&&e!==i){if("open"!==t)return"view"===t?(this.sync(),void this.dispatch("db-view-change",{view:this.view})):void this.sync();this.syncOpenState()}}hydrateFromAttributes(){this._messages=parseJson(this.getAttribute("messages"),this._messages),this._conversations=parseJson(this.getAttribute("conversations"),this._conversations),this._menuItems=parseJson(this.getAttribute("menu-items"),this._menuItems),this._quickReplies=parseJson(this.getAttribute("quick-replies"),this._quickReplies),this._articles=parseJson(this.getAttribute("articles"),this._articles)}build(){if(this.built)return;this.built=!0,this.classList.add(o),this.replaceChildren(),this.launcher=el("button",`${o}__launcher`),this.launcher.type="button",this.launcher.setAttribute("aria-haspopup","dialog");const t=el("span",`${o}__launcher-icon ${o}__launcher-icon--open`),e=el("db-chat-orb");e.setAttribute("size","s"),e.setAttribute("variant",this.getAttribute("orb-variant")||"violet"),e.setAttribute("state","idle"),t.append(e);const i=el("span",`${o}__launcher-icon ${o}__launcher-icon--close`);i.append(widgetIcon("close-x",24)),this.launcherBadge=document.createElement("db-badge"),this.launcherBadge.className=`${o}__launcher-badge`,this.launcherBadge.setAttribute("type","number"),this.launcherBadge.setAttribute("variant","danger"),this.launcherBadge.setAttribute("size","s"),this.launcherBadge.setAttribute("max-count","99"),this.launcher.append(t,i),this.launcher.addEventListener("click",()=>this.toggle()),this.panel=el("div",`${o}__panel`),this.panel.setAttribute("role","dialog"),this.panel.setAttribute("aria-modal","false"),this.headerEl=el("div",`${o}__header`),this.bodyEl=el("div",`${o}__body`),this.footerEl=el("div",`${o}__footer`),this.navEl=el("div",`${o}__nav`),this.panel.append(this.headerEl,this.bodyEl,this.footerEl,this.navEl),this.append(this.panel,this.launcher,this.launcherBadge),this.bodyEl.addEventListener("dragenter",this.handlePanelDragEnter),this.bodyEl.addEventListener("dragover",this.handlePanelDragOver),this.bodyEl.addEventListener("dragleave",this.handlePanelDragLeave),this.bodyEl.addEventListener("drop",this.handlePanelDrop)}sync(){a.forEach(t=>this.classList.toggle(`${o}--${t}`,t===this.position)),s.forEach(t=>this.classList.toggle(`${o}--view-${t}`,t===this.view));const t=this.getAttribute("accent");t?this.style.setProperty("--dbchat-accent",t):this.style.removeProperty("--dbchat-accent"),this.renderBadge(),this.renderHeader(),this.renderBody(),this.renderFooter(),this.renderNav(),this.syncOpenState()}syncOpenState(){const t=this.open;this.classList.toggle(`${o}--open`,t),this.launcher.setAttribute("aria-expanded",String(t)),this.panel.setAttribute("aria-hidden",String(!t)),t?this.panel.removeAttribute("inert"):this.panel.setAttribute("inert",""),this.launcher.setAttribute("aria-label",this.getAttribute("launcher-label")||(t?"상담창 닫기":"상담 시작하기")),t&&"conversation"===this.view&&requestAnimationFrame(()=>this.composer?.focusInput())}renderBadge(){const t=this.unread;t>0?this.launcherBadge.setAttribute("count",String(t)):this.launcherBadge.removeAttribute("count"),this.launcherBadge.hidden=t<=0}renderHeader(){this.headerEl.replaceChildren();const t=this.view,i=this.getAttribute("agent-name")||"디지털 상담원",s=el("div",`${o}__header-bar`);if("conversation"===t){const t=this.iconButton(e("chevron-left",20),"뒤로",()=>this.setView("messages")),a=el("div",`${o}__header-identity`),n=this.getAttribute("agent-avatar"),r=el(n?"db-avatar":"db-chat-orb");n?(r.setAttribute("src",n),r.setAttribute("alt",i),r.setAttribute("size","m")):(r.className=`${o}__header-orb`,r.setAttribute("size","xs"),r.setAttribute("variant",this.getAttribute("orb-variant")||"violet"));const l=el("div",`${o}__header-meta`);l.append(el("span",`${o}__header-name`,i),el("span",`${o}__header-status`,this.getAttribute("agent-status")||"보통 몇 분 내 응답")),a.append(r,l),s.append(t,a,this.iconButton(widgetIcon("close-x",20),"닫기",()=>this.close()))}else if("article"===t){const t=this.iconButton(e("chevron-left",20),"뒤로",()=>{this._activeArticle=null,this.setView("help")});s.append(t,el("span",`${o}__header-title`,this._activeArticle?.title||"도움말"),this.iconButton(widgetIcon("close-x",20),"닫기",()=>this.close()))}else{const e="home"===t?"홈":"messages"===t?"메시지":"도움말";s.append(el("span",`${o}__header-title`,e),this.iconButton(widgetIcon("close-x",20),"닫기",()=>this.close()))}this.headerEl.append(s)}iconButton(t,e,i){const s=el("button",`${o}__icon-btn`);return s.type="button",s.setAttribute("aria-label",e),s.append(t),s.addEventListener("click",i),s}renderBody(){if(this.built)switch(this.threadEl=null,this.bodyEl.replaceChildren(),this.bodyEl.classList.toggle(`${o}__body--thread`,"conversation"===this.view),this.bodyEl.classList.toggle(`${o}__body--article`,"article"===this.view),this.view){case"home":this.renderHome();break;case"messages":this.renderMessages();break;case"conversation":this.renderConversation();break;case"help":this.renderHelp();break;case"article":this.renderArticle()}}renderHome(){const t=el("db-chat-welcome",`${o}__welcome`);t.setAttribute("greeting",this.getAttribute("greeting")||"안녕하세요"),t.setAttribute("title",this.getAttribute("title")||"무엇을 도와드릴까요?"),t.setAttribute("highlight",this.getAttribute("highlight")||this.getAttribute("title")||"도와드릴까요"),t.setAttribute("subtitle",this.getAttribute("subtitle")||"제품·정책을 24시간 정확하게 안내해 드릴게요."),t.setAttribute("orb-variant",this.getAttribute("orb-variant")||"violet"),t.setAttribute("align","left"),t.setAttribute("orb-size","m"),this.bodyEl.append(t);const i=this._conversations[0];if(i){const t=el("button",`${o}__recent`);t.type="button";const s=el("db-avatar",`${o}__recent-avatar`);s.setAttribute("size","s"),s.setAttribute("variant","primary"),i.avatarSrc?s.setAttribute("src",i.avatarSrc):s.setAttribute("initials",i.avatarInitials||i.name.charAt(0)||"상"),i.online&&s.setAttribute("status","online"),i.unread&&(s.setAttribute("badge",String(i.unread)),s.setAttribute("type","number"),s.setAttribute("badge-variant","primary"),s.setAttribute("badge-max-count","99"));const a=el("div",`${o}__recent-body`);a.append(el("span",`${o}__recent-title`,"최근 대화 이어가기"),el("span",`${o}__recent-preview`,i.preview||"")),t.append(s,a,e("chevron-right",18)),t.addEventListener("click",()=>{this.dispatch("db-conversation-select",{id:i.id}),this.setView("conversation")}),this.bodyEl.append(t)}else this.bodyEl.append(this.buildStartChatButton("대화 시작하기","primary"));if(this._menuItems.length){const t=el("db-chat-suggestions",`${o}__menu`);t.setAttribute("layout","card"),t.setAttribute("columns","1"),t.items=this._menuItems,t.addEventListener("db-select",t=>{t.stopPropagation();const e=t.detail,i=this._menuItems.find(t=>(t.id??t.title)===e.id),s=i?.action??"none";this.dispatch("db-menu-select",{...e,action:s}),"conversation"===s?this.startConversation():"help"===s&&this.setView("help")}),this.bodyEl.append(t)}}renderMessages(){if(!this._conversations.length)return void this.bodyEl.append(this.buildEmpty("아직 대화가 없어요","궁금한 점이 있으면 새 대화를 시작해 보세요.",{ctaLabel:t("대화 시작하기"),onCta:()=>this.startConversation()}));const e=el("div",`${o}__conv-list`);this._conversations.forEach(t=>{const i=el("button",`${o}__conv`);i.type="button";const s=el("db-avatar",`${o}__conv-avatar`);s.setAttribute("size","m"),s.setAttribute("variant","primary"),t.avatarSrc?s.setAttribute("src",t.avatarSrc):s.setAttribute("initials",t.avatarInitials||t.name.charAt(0)||"?"),t.online&&s.setAttribute("status","online");const a=el("div",`${o}__conv-body`),n=el("div",`${o}__conv-top`);if(n.append(el("span",`${o}__conv-name`,t.name)),t.time&&n.append(el("span",`${o}__conv-time`,t.time)),a.append(n,el("span",`${o}__conv-preview`,t.preview||"")),i.append(s,a),t.unread){const e=el("db-badge",`${o}__conv-unread`);e.setAttribute("type","number"),e.setAttribute("variant","primary"),e.setAttribute("size","s"),e.setAttribute("max-count","99"),e.setAttribute("count",String(t.unread)),i.append(e)}i.addEventListener("click",()=>{this.dispatch("db-conversation-select",{id:t.id}),this.setView("conversation")}),e.append(i)}),this.bodyEl.append(e)}renderConversation(){const t=el("div",`${o}__thread`);if(this._messages.forEach(e=>t.append(this.buildMessage(e))),this.threadEl=t,this.bodyEl.append(t),this.typing&&t.append(this.buildTyping()),this._quickReplies.length){const t=el("db-chat-suggestions");t.setAttribute("data-dbchat","quick"),t.setAttribute("layout","chip"),t.items=this._quickReplies,t.addEventListener("db-select",t=>{t.stopPropagation();const e=t.detail;this.dispatch("db-quick-reply",e),this.addMessage({role:"user",text:e.title,time:this.nowLabel()})}),this.bodyEl.append(t)}requestAnimationFrame(()=>this.scrollThreadToEnd())}renderHelp(){const i=el("div",`${o}__help-search`),s=el("span",`${o}__help-search-icon`);s.append(e("search",18));const a=el("input",`${o}__help-input`);a.type="search",a.placeholder=t("도움말 검색…"),a.value=this.helpQuery,a.addEventListener("input",()=>{this.helpQuery=a.value,this.renderHelpResults()}),i.append(s,a),this.bodyEl.append(i);const n=el("div",`${o}__help-results`);this.bodyEl.append(n),this.renderHelpResults()}renderHelpResults(){const t=this.bodyEl.querySelector(`.${o}__help-results`);if(!t)return;t.replaceChildren();const e=this.helpQuery.trim().toLowerCase(),i=e?this._articles.filter(t=>t.title.toLowerCase().includes(e)||(t.description||"").toLowerCase().includes(e)||(t.body||"").toLowerCase().includes(e)):this._articles;if(!i.length)return void t.append(this.buildEmpty(e?"검색 결과가 없어요":"문서가 없어요",e?"다른 검색어로 다시 시도해 보세요.":"도움말 문서가 준비되면 여기에 표시됩니다."));const s=el("db-chat-suggestions",`${o}__articles`);s.setAttribute("layout","card"),s.setAttribute("columns","1"),s.items=i,s.addEventListener("db-select",t=>{t.stopPropagation();const e=t.detail,s=this._articles.find(t=>(t.id??t.title)===e.id)||i.find(t=>(t.id??t.title)===e.id);this.dispatch("db-article-select",{id:e.id,title:e.title}),s&&this.openArticle(s)}),t.append(s)}renderArticle(){const t=this._activeArticle;if(!t)return void this.setView("help");const s=el("article",`${o}__article`);if(t.icon&&i(t.icon)){const i=el("div",`${o}__article-icon`);i.append(e(t.icon,22)),s.append(i)}s.append(el("h2",`${o}__article-title`,t.title)),t.description&&s.append(el("p",`${o}__article-desc`,t.description));const a=el("div",`${o}__article-body`);(t.body||t.description||"").split(/\n{2,}/).forEach(t=>{const e=el("p",void 0,t.trim());e.textContent&&a.append(e)}),s.append(a);const n=this.buildStartChatButton("이 내용으로 문의하기","secondary");s.append(n),this.bodyEl.append(s)}openArticle(t){this._activeArticle=t,this.setView("article")}startConversation(){this.dispatch("db-start-chat"),this.setView("conversation")}buildMessage(t){const e="user"===t.role?"user":"assistant",i=el("db-chat-message");i.setAttribute("role",e),t.name&&i.setAttribute("name",t.name),t.time&&i.setAttribute("timestamp",t.time);const s=t.avatarSrc||("assistant"===e?this.getAttribute("agent-avatar"):null);return s&&i.setAttribute("avatar-src",s),t.avatarInitials&&i.setAttribute("avatar-initials",t.avatarInitials),t.actions&&i.setAttribute("actions",t.actions),i.textContent=t.text||"",t.attachments?.length&&(i.attachments=t.attachments),i.addEventListener("db-action",t=>{t.stopPropagation(),this.dispatch("db-message-action",t.detail)}),i}buildTyping(){const t=el("db-chat-typing",`${o}__typing`);t.setAttribute("name",this.getAttribute("agent-name")||"상담원");const e=this.getAttribute("agent-avatar");return e&&t.setAttribute("avatar-src",e),t.setAttribute("label","입력 중"),t}buildEmpty(t,e,i){const s=el("div",`${o}__empty`),a=el("db-chat-orb",`${o}__empty-orb`);return a.setAttribute("size","m"),a.setAttribute("variant",this.getAttribute("orb-variant")||"violet"),s.append(a,el("p",`${o}__empty-title`,t),el("p",`${o}__empty-desc`,e)),i?.ctaLabel&&i.onCta&&s.append(this.buildStartChatButton(i.ctaLabel,"primary",i.onCta)),s}buildStartChatButton(t,e="primary",i){const s=el("button",`${o}__cta ${o}__cta--${e}`);return s.type="button",s.append(widgetIcon("chat",18),el("span",void 0,t)),s.addEventListener("click",()=>i?i():this.startConversation()),s}renderFooter(){const t="conversation"===this.view;if(this.footerEl.hidden=!t,!t)return this.composer=null,void this.footerEl.replaceChildren();if(!this.composer){this.footerEl.replaceChildren();const t=el("db-chat-composer",`${o}__composer`);t.setAttribute("placeholder",this.getAttribute("placeholder")||"메시지를 입력하세요…"),t.setAttribute("attach",""),t.setAttribute("voice",""),t.addEventListener("db-send",t=>{t.stopPropagation();const e=t.detail,i=e?.value?.trim()||"",s=e?.attachments||[];if(!i&&!s.length)return;const a=s.map(t=>({name:t.name,kind:t.kind,mime:t.mime,url:t.file?URL.createObjectURL(t.file):t.preview}));this.dispatch("db-send",{text:i,attachments:a}),this.addMessage({role:"user",text:i,time:this.nowLabel(),attachments:a.length?a:void 0})}),t.addEventListener("db-stop",t=>{t.stopPropagation(),this.dispatch("db-stop")}),t.addEventListener("db-attach",t=>{t.stopPropagation(),this.dispatch("db-attach")}),t.addEventListener("db-voice",t=>{t.stopPropagation(),this.dispatch("db-voice")}),this.composer=t,this.footerEl.append(t)}}renderNav(){this.navEl.replaceChildren();const e="conversation"!==this.view&&"article"!==this.view;if(this.navEl.hidden=!e,!e)return;[{view:"home",icon:"home",label:t("홈")},{view:"messages",icon:"chat",label:t("메시지")},{view:"help",icon:"help",label:t("도움말")}].forEach(t=>{const e=el("button",`${o}__tab`);e.type="button";const i=this.view===t.view;if(e.classList.toggle(`${o}__tab--active`,i),e.setAttribute("aria-current",String(i)),e.append(widgetIcon(t.icon,22),el("span",`${o}__tab-label`,t.label)),"messages"===t.view&&this.unread>0){const t=el("db-badge",`${o}__tab-badge`);t.setAttribute("type","dot"),t.setAttribute("variant","danger"),t.setAttribute("size","s"),e.append(t)}e.addEventListener("click",()=>{"help"===t.view&&(this.helpQuery=""),this.setView(t.view)}),this.navEl.append(e)})}scrollThreadToEnd(){this.bodyEl.scrollTop=this.bodyEl.scrollHeight}nowLabel(){const t=new Date,e=t.getHours();return`${e<12?"오전":"오후"} ${e%12==0?12:e%12}:${t.getMinutes().toString().padStart(2,"0")}`}dispatch(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}openWidget(){this.open||(this.open=!0,this.dispatch("db-open"),this.dispatch("db-toggle",{open:!0}))}close(){this.open&&(this.open=!1,this.dispatch("db-close"),this.dispatch("db-toggle",{open:!1}))}toggle(){this.open?this.close():this.openWidget()}setView(t){this.view!==t&&(this.view=t)}addMessage(t){if(this._messages=[...this._messages,{id:`m${Date.now()}`,...t}],"conversation"===this.view)if(this.built&&this.threadEl){const e=this.threadEl.querySelector("db-chat-typing");this.threadEl.insertBefore(this.buildMessage(t),e),requestAnimationFrame(()=>this.scrollThreadToEnd())}else this.built&&this.renderBody();else this.view="conversation"}setTyping(t){if(this.typing=Boolean(t),!this.built||"conversation"!==this.view||!this.threadEl)return;const e=this.threadEl.querySelector("db-chat-typing");this.typing&&!e?(this.threadEl.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollThreadToEnd())):!this.typing&&e&&e.remove()}setStreaming(t){this.composer?.setStreaming(Boolean(t))}setMessages(t){this.messages=t}}customElements.get("db-chat-widget")||customElements.define("db-chat-widget",DbChatWidget);export{DbChatWidget};
|