@designbasekorea/ui-wc 0.5.5 → 0.6.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/dist/esm/components/animation-background/layers.js +1 -1
- package/dist/esm/components/db-ai-chat-list.d.ts +43 -0
- package/dist/esm/components/db-ai-chat-list.js +1 -0
- package/dist/esm/components/db-ai-chat.d.ts +159 -0
- package/dist/esm/components/db-ai-chat.js +1 -0
- package/dist/esm/components/db-animation-background.js +1 -1
- package/dist/esm/components/db-chat-composer.d.ts +134 -0
- package/dist/esm/components/db-chat-composer.js +1 -0
- package/dist/esm/components/db-chat-list.d.ts +47 -0
- package/dist/esm/components/db-chat-list.js +1 -0
- package/dist/esm/components/db-chat-message.d.ts +39 -0
- package/dist/esm/components/db-chat-message.js +1 -0
- package/dist/esm/components/db-chat-orb.d.ts +99 -0
- package/dist/esm/components/db-chat-orb.js +1 -0
- package/dist/esm/components/db-chat-room.d.ts +71 -0
- package/dist/esm/components/db-chat-room.js +1 -0
- package/dist/esm/components/db-chat-suggestions.d.ts +44 -0
- package/dist/esm/components/db-chat-suggestions.js +1 -0
- package/dist/esm/components/db-chat-typing.d.ts +22 -0
- package/dist/esm/components/db-chat-typing.js +1 -0
- package/dist/esm/components/db-chat-welcome.d.ts +32 -0
- package/dist/esm/components/db-chat-welcome.js +1 -0
- package/dist/esm/components/db-chat-widget.d.ts +146 -0
- package/dist/esm/components/db-chat-widget.js +1 -0
- package/dist/esm/components/db-chip.js +1 -1
- package/dist/esm/components/db-download-progress.d.ts +89 -0
- package/dist/esm/components/db-download-progress.js +1 -0
- package/dist/esm/components/db-email-verify.d.ts +62 -0
- package/dist/esm/components/db-email-verify.js +1 -0
- package/dist/esm/components/db-icons.d.ts +3 -0
- package/dist/esm/components/db-icons.js +1 -1
- package/dist/esm/components/db-logo-svg.js +1 -1
- package/dist/esm/components/db-logo.js +1 -1
- package/dist/esm/components/db-otp-input.d.ts +53 -0
- package/dist/esm/components/db-otp-input.js +1 -0
- package/dist/esm/components/db-phone-verify.d.ts +61 -0
- package/dist/esm/components/db-phone-verify.js +1 -0
- package/dist/esm/components/db-testimonial.d.ts +2 -0
- package/dist/esm/components/db-testimonial.js +1 -1
- package/dist/esm/components/db-two-factor.d.ts +60 -0
- package/dist/esm/components/db-two-factor.js +1 -0
- package/dist/esm/components/highlight-code.js +1 -1
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1091 -2
- package/dist/index.esm.js +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +1620 -54
- package/dist/react/index.esm.js +1 -1
- package/package.json +1 -1
- /package/dist/chunks/{lottie-2637aa0f.js → lottie-0e23d630.js} +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-bash.js +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-clike.js +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-css.js +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-javascript.js +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-json.js +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-jsx.js +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-markup.js +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-scss.js +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-tsx.js +0 -0
- /package/dist/esm/node_modules/{prismjs → .pnpm/prismjs@1.30.0/node_modules/prismjs}/components/prism-typescript.js +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
declare const SIZES: readonly ["xs", "s", "m", "l", "xl"];
|
|
2
|
+
declare const VARIANTS: readonly ["aurora", "ocean", "emerald", "violet", "mono"];
|
|
3
|
+
declare const STATES: readonly ["idle", "thinking", "listening"];
|
|
4
|
+
declare const THEMES: readonly ["auto", "light", "dark"];
|
|
5
|
+
type OrbSize = (typeof SIZES)[number];
|
|
6
|
+
type OrbVariant = (typeof VARIANTS)[number];
|
|
7
|
+
type OrbState = (typeof STATES)[number];
|
|
8
|
+
type OrbTheme = (typeof THEMES)[number];
|
|
9
|
+
/**
|
|
10
|
+
* AI 어시스턴트를 상징하는 캔버스 기반 애니메이션 오브(구체).
|
|
11
|
+
* 광택 있는 3D 구체 위에 유체처럼 흐르는 내부 광원 + 프레넬 림 라이트 + 스페큘러 하이라이트로
|
|
12
|
+
* 고품질 오브를 렌더링합니다. `state` 에 따라 모양(진폭·난류)·색(밝기)·인터랙션(리플)이 바뀌며,
|
|
13
|
+
* 포인터를 올리면 하이라이트와 내부 광원이 살짝 따라옵니다. 순수 장식이라 `aria-hidden` 입니다.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <db-chat-orb variant="aurora" state="thinking"></db-chat-orb>
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const orb = document.querySelector('db-chat-orb');
|
|
20
|
+
* orb.state = 'listening';
|
|
21
|
+
* orb.minScale = 0.86;
|
|
22
|
+
* orb.maxScale = 1.2;
|
|
23
|
+
* orb.audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
24
|
+
*/
|
|
25
|
+
declare class DbChatOrb extends HTMLElement {
|
|
26
|
+
static get observedAttributes(): string[];
|
|
27
|
+
private canvas;
|
|
28
|
+
private ctx;
|
|
29
|
+
private dpr;
|
|
30
|
+
private cssW;
|
|
31
|
+
private cssH;
|
|
32
|
+
private t0;
|
|
33
|
+
private lastFrame;
|
|
34
|
+
private resizeObs;
|
|
35
|
+
private io;
|
|
36
|
+
private themeObserver;
|
|
37
|
+
private themeMedia;
|
|
38
|
+
private visible;
|
|
39
|
+
private hover;
|
|
40
|
+
private ripples;
|
|
41
|
+
private lastAutoRipple;
|
|
42
|
+
private lastVoiceRipple;
|
|
43
|
+
private previousVoiceLevel;
|
|
44
|
+
private displayedVoiceLevel;
|
|
45
|
+
private inputStream;
|
|
46
|
+
private audioContext;
|
|
47
|
+
private audioSource;
|
|
48
|
+
private analyser;
|
|
49
|
+
private analyserData;
|
|
50
|
+
get size(): OrbSize;
|
|
51
|
+
set size(v: OrbSize);
|
|
52
|
+
get variant(): OrbVariant;
|
|
53
|
+
set variant(v: OrbVariant);
|
|
54
|
+
get state(): OrbState;
|
|
55
|
+
set state(v: OrbState);
|
|
56
|
+
get theme(): OrbTheme;
|
|
57
|
+
set theme(v: OrbTheme);
|
|
58
|
+
/** 외부 오디오 분석기가 전달하는 정규화 음성 레벨(0–1). */
|
|
59
|
+
get voiceLevel(): number;
|
|
60
|
+
set voiceLevel(v: number);
|
|
61
|
+
/** listening 무음 구간의 최소 구체 배율. */
|
|
62
|
+
get minScale(): number;
|
|
63
|
+
set minScale(v: number);
|
|
64
|
+
/** listening 최대 음량 구간의 최대 구체 배율. */
|
|
65
|
+
get maxScale(): number;
|
|
66
|
+
set maxScale(v: number);
|
|
67
|
+
/**
|
|
68
|
+
* 실제 마이크/통화 MediaStream. 내부 AnalyserNode로 RMS를 계산하며 오디오를 재생하거나
|
|
69
|
+
* 전달받은 트랙을 stop()하지 않습니다. null을 할당하면 분석만 해제합니다.
|
|
70
|
+
*/
|
|
71
|
+
get audioStream(): MediaStream | null;
|
|
72
|
+
set audioStream(stream: MediaStream | null);
|
|
73
|
+
connectedCallback(): void;
|
|
74
|
+
disconnectedCallback(): void;
|
|
75
|
+
attributeChangedCallback(name: string): void;
|
|
76
|
+
private setVisible;
|
|
77
|
+
private syncClasses;
|
|
78
|
+
private build;
|
|
79
|
+
private onPointerMove;
|
|
80
|
+
private onPointerLeave;
|
|
81
|
+
private onPointerDown;
|
|
82
|
+
private addRipple;
|
|
83
|
+
private observeTheme;
|
|
84
|
+
private onThemeChange;
|
|
85
|
+
private isDarkContext;
|
|
86
|
+
private startAudioAnalysis;
|
|
87
|
+
private stopAudioAnalysis;
|
|
88
|
+
private sampleVoiceLevel;
|
|
89
|
+
private resize;
|
|
90
|
+
/** 공유 틱커가 호출 — 한 프레임 렌더 */
|
|
91
|
+
frame(now: number): void;
|
|
92
|
+
}
|
|
93
|
+
declare global {
|
|
94
|
+
interface HTMLElementTagNameMap {
|
|
95
|
+
'db-chat-orb': DbChatOrb;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { DbChatOrb };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=["xs","s","m","l","xl"],e=["aurora","ocean","emerald","violet","mono"],i=["idle","thinking","listening"],s=["auto","light","dark"];function pick(t,e,i){return t.includes(e)?e:i}const a={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]}},r={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 n=Number(r);return Number.isFinite(n)?clamp(n,s,a):i}const h=2*Math.PI,o="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,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=()=>{o&&this.frame(performance.now())}}static get observedAttributes(){return["size","variant","state","theme","voice-level","min-scale","max-scale"]}get size(){return pick(t,this.getAttribute("size"),"l")}set size(t){this.setAttribute("size",t)}get variant(){return pick(e,this.getAttribute("variant"),"aurora")}set variant(t){this.setAttribute("variant",t)}get state(){return pick(i,this.getAttribute("state"),"idle")}set state(t){this.setAttribute("state",t)}get theme(){return pick(s,this.getAttribute("theme"),"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(),o?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(),o&&this.frame(performance.now()))}setVisible(t){t!==this.visible&&(this.visible=t,t?(l.add(this),function ensureTicking(){c||!l.size||o||(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),o&&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,o=this.cssW,l=this.cssH,c=o/2,d=l/2,m=Math.min(o,l)/2,u=this.isDarkContext(),p=u?a[this.variant]:r[this.variant],g=n[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,hasInput:!1},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),A=1+Math.sin(s*g.breatheSpeed)*w,S="listening"===this.state?f:"thinking"===this.state?1:.55,M="thinking"===this.state?1.38:1,C=.68*m*A*y*("thinking"===this.state?1.1:1),x=.99*m;e.setTransform(this.dpr,0,0,this.dpr,0,0),e.clearRect(0,0,o,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),z=mix(p.core,p.lights[1],u?"thinking"===this.state?.35:0:"thinking"===this.state?.28:.12),L=e.createRadialGradient(c,d-.12*C,.1*C,c,d,C);L.addColorStop(0,rgba(O,u?1:.96)),L.addColorStop(.72,rgba(z,u?1:.9)),L.addColorStop(.93,rgba(z,u?.72:.5)),L.addColorStop(1,rgba(z,0)),e.fillStyle=L,e.beginPath(),e.arc(c,d,C,0,h),e.fill(),e.globalCompositeOperation=u?"lighter":"source-over";const R=p.lights.length;for(let t=0;t<R;t+=1){const i=2.1*t,a=g.turb*(.85+.55*S),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/R*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*M*(.92+.28*S+.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*S)*("thinking"===this.state?1.22:1);e.save(),e.filter=`blur(${C*(u?.15:.18)*("thinking"===this.state?.92:1)}px)`;const A=e.createRadialGradient(b,v,0,b,v,f);A.addColorStop(0,rgba(y,Math.min(1,w))),A.addColorStop(.48,rgba(y,Math.min(1,.5*w))),A.addColorStop(1,rgba(y,0)),e.fillStyle=A,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*S);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};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import './db-avatar.js';
|
|
2
|
+
import './db-chat-message.js';
|
|
3
|
+
import './db-chat-typing.js';
|
|
4
|
+
import { DbChatComposer } from './db-chat-composer.js';
|
|
5
|
+
|
|
6
|
+
interface DbChatRoomMessage {
|
|
7
|
+
id?: string;
|
|
8
|
+
/** 내가 보낸 메시지면 true(우측 정렬). 아니면 상대(좌측) */
|
|
9
|
+
mine?: boolean;
|
|
10
|
+
text?: string;
|
|
11
|
+
html?: string;
|
|
12
|
+
time?: string;
|
|
13
|
+
/** 상대 이름(그룹 채팅에서 유용) */
|
|
14
|
+
name?: string;
|
|
15
|
+
avatarSrc?: string;
|
|
16
|
+
avatarInitials?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 사람 간 1:1/그룹 대화 화면. 상단 헤더(상대 정보) + 메시지 스레드 + 하단 컴포저.
|
|
20
|
+
* 메시지·아바타·입력창은 `db-chat-message` · `db-avatar` · `db-chat-composer` 를 재사용합니다.
|
|
21
|
+
* AI 대화 화면은 `db-ai-chat` 을 쓰세요.
|
|
22
|
+
*
|
|
23
|
+
* @fires db-send - 전송 (detail: { value, attachments })
|
|
24
|
+
* @fires db-back - 뒤로가기
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const room = document.querySelector('db-chat-room');
|
|
28
|
+
* room.setAttribute('peer-name', '디지털 상담원');
|
|
29
|
+
* room.messages = [{ mine:false, text:'무엇을 도와드릴까요?', time:'오후 2:31' }];
|
|
30
|
+
* room.addEventListener('db-send', (e) => room.addMessage({ mine:true, text:e.detail.value }));
|
|
31
|
+
*/
|
|
32
|
+
declare class DbChatRoom extends HTMLElement {
|
|
33
|
+
static get observedAttributes(): string[];
|
|
34
|
+
private built;
|
|
35
|
+
private typing;
|
|
36
|
+
private _messages;
|
|
37
|
+
private headerEl;
|
|
38
|
+
private scrollEl;
|
|
39
|
+
private threadEl;
|
|
40
|
+
private dockEl;
|
|
41
|
+
private composer;
|
|
42
|
+
set messages(v: DbChatRoomMessage[]);
|
|
43
|
+
get messages(): DbChatRoomMessage[];
|
|
44
|
+
connectedCallback(): void;
|
|
45
|
+
attributeChangedCallback(): void;
|
|
46
|
+
private build;
|
|
47
|
+
private sync;
|
|
48
|
+
private renderHeader;
|
|
49
|
+
private renderThread;
|
|
50
|
+
private buildMessage;
|
|
51
|
+
private buildTyping;
|
|
52
|
+
private scrollToEnd;
|
|
53
|
+
/** 메시지 추가. 추가된 id 반환 */
|
|
54
|
+
addMessage(msg: DbChatRoomMessage): string;
|
|
55
|
+
/** 전체 메시지 교체 */
|
|
56
|
+
setMessages(list: DbChatRoomMessage[]): void;
|
|
57
|
+
/** 상대 입력중(…) 표시 토글 */
|
|
58
|
+
setTyping(on: boolean): void;
|
|
59
|
+
/** 입력창 포커스 */
|
|
60
|
+
focusInput(): void;
|
|
61
|
+
/** 컴포저 인스턴스 접근 */
|
|
62
|
+
getComposer(): DbChatComposer;
|
|
63
|
+
}
|
|
64
|
+
declare global {
|
|
65
|
+
interface HTMLElementTagNameMap {
|
|
66
|
+
'db-chat-room': DbChatRoom;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { DbChatRoom };
|
|
71
|
+
export type { DbChatRoomMessage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createDbIcon as t}from"./db-icons.js";import"./db-avatar.js";import"./db-chat-message.js";import"./db-chat-typing.js";import"./db-chat-composer.js";const e="designbase-wc-chat-room";let s=0;function el(t,e,s){const i=document.createElement(t);return e&&(i.className=e),null!=s&&(i.textContent=s),i}class DbChatRoom extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.typing=!1,this._messages=[]}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${s+=1}`,...t})):[],this.built&&this.renderThread()}get messages(){return this._messages}connectedCallback(){this.built||this.build(),this.sync()}attributeChangedCallback(){this.built&&this.sync()}build(){this.built=!0,this.className=e,this.replaceChildren(),this.headerEl=el("div",`${e}__header`),this.scrollEl=el("div",`${e}__scroll`),this.threadEl=el("div",`${e}__thread`),this.scrollEl.append(this.threadEl),this.dockEl=el("div",`${e}__dock`),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;this.dispatchEvent(new CustomEvent("db-send",{detail:e,bubbles:!0,composed:!0}))}),this.dockEl.append(this.composer),this.append(this.headerEl,this.scrollEl,this.dockEl),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",`${e}__back`);s.type="button",s.setAttribute("aria-label","뒤로"),s.append(t("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=`${e}__avatar`,s.setAttribute("size","m"),s.setAttribute("variant","primary");const i=this.getAttribute("peer-avatar"),a=this.getAttribute("peer-initials");i?s.setAttribute("src",i):a&&s.setAttribute("initials",a),this.hasAttribute("online")&&s.setAttribute("status","online");const r=el("div",`${e}__meta`);r.append(el("span",`${e}__name`,this.getAttribute("peer-name")||"대화"));const n=this.getAttribute("peer-status")||(this.hasAttribute("online")?"온라인":"");n&&r.append(el("span",`${e}__status`,n)),this.headerEl.append(s,r)}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");return 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),!t.mine&&t.avatarSrc&&e.setAttribute("avatar-src",t.avatarSrc),!t.mine&&t.avatarInitials&&e.setAttribute("avatar-initials",t.avatarInitials),null!=t.html?e.innerHTML=t.html:e.textContent=t.text||"",e}buildTyping(){const t=el("db-chat-typing");return t.setAttribute("name",this.getAttribute("peer-name")||""),t.setAttribute("avatar-initials",this.getAttribute("peer-initials")||""),t}scrollToEnd(){this.scrollEl.scrollTop=this.scrollEl.scrollHeight}addMessage(t){const e={id:`r${s+=1}`,...t};this._messages.push(e);const i=this.threadEl.querySelector("db-chat-typing"),a=this.buildMessage(e);return i?this.threadEl.insertBefore(a,i):this.threadEl.append(a),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};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DbIconName } from './db-icons.js';
|
|
2
|
+
import './db-chip.js';
|
|
3
|
+
|
|
4
|
+
interface DbChatSuggestion {
|
|
5
|
+
/** 고유 id */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** 제목 (카드/칩 라벨) */
|
|
8
|
+
title: string;
|
|
9
|
+
/** 설명 (카드 레이아웃에서만 표시) */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** 아이콘 (DbIconName) */
|
|
12
|
+
icon?: DbIconName;
|
|
13
|
+
/** 선택 시 전달될 값 (기본값: title) */
|
|
14
|
+
value?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 채팅 시작 화면의 추천 프롬프트 목록.
|
|
18
|
+
* `layout="card"` 는 아이콘·제목·설명이 있는 카드 그리드,
|
|
19
|
+
* `layout="chip"` 은 클릭 가능한 칩(db-chip 재사용) 행으로 렌더링합니다.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const s = document.querySelector('db-chat-suggestions');
|
|
23
|
+
* s.items = [{ title:'Write Copy', description:'...', icon:'edit' }];
|
|
24
|
+
* s.addEventListener('db-select', (e) => console.log(e.detail.value));
|
|
25
|
+
*/
|
|
26
|
+
declare class DbChatSuggestions extends HTMLElement {
|
|
27
|
+
static get observedAttributes(): string[];
|
|
28
|
+
private _items;
|
|
29
|
+
get items(): DbChatSuggestion[];
|
|
30
|
+
set items(list: DbChatSuggestion[]);
|
|
31
|
+
get layout(): string;
|
|
32
|
+
connectedCallback(): void;
|
|
33
|
+
attributeChangedCallback(): void;
|
|
34
|
+
private select;
|
|
35
|
+
private render;
|
|
36
|
+
}
|
|
37
|
+
declare global {
|
|
38
|
+
interface HTMLElementTagNameMap {
|
|
39
|
+
'db-chat-suggestions': DbChatSuggestions;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { DbChatSuggestions };
|
|
44
|
+
export type { DbChatSuggestion };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isDbIconName as t,createDbIcon as e}from"./db-icons.js";import"./db-chip.js";const s=["card","chip"];class DbChatSuggestions extends HTMLElement{constructor(){super(...arguments),this._items=[]}static get observedAttributes(){return["layout","columns"]}get items(){return this._items}set items(t){this._items=Array.isArray(t)?t:[],this.render()}get layout(){return s.includes(this.getAttribute("layout"))?this.getAttribute("layout"):"card"}connectedCallback(){if(!this._items.length&&this.hasAttribute("items"))try{this._items=JSON.parse(this.getAttribute("items")||"[]")}catch{this._items=[]}this.render()}attributeChangedCallback(){this.render()}select(t){this.dispatchEvent(new CustomEvent("db-select",{detail:{id:t.id??t.title,title:t.title,value:t.value??t.title},bubbles:!0,composed:!0}))}render(){const s=this.layout,i=Number(this.getAttribute("columns")||3);if(this.className=`designbase-wc-chat-suggestions designbase-wc-chat-suggestions--${s}`,this.style.setProperty("--dbcs-columns",String(i)),this.replaceChildren(),"chip"!==s)for(const s of this._items){const i=document.createElement("button");i.type="button",i.className="designbase-wc-chat-suggestions__card";const n=document.createElement("span");n.className="designbase-wc-chat-suggestions__icon",n.setAttribute("aria-hidden","true"),s.icon&&t(s.icon)&&n.append(e(s.icon,20));const c=document.createElement("span");c.className="designbase-wc-chat-suggestions__body";const a=document.createElement("span");if(a.className="designbase-wc-chat-suggestions__title",a.textContent=s.title,c.append(a),s.description){const t=document.createElement("span");t.className="designbase-wc-chat-suggestions__desc",t.textContent=s.description,c.append(t)}i.append(n,c),i.addEventListener("click",()=>this.select(s)),this.append(i)}else for(const e of this._items){const s=document.createElement("db-chip");s.className="designbase-wc-chat-suggestions__chip",s.setAttribute("label",e.title),s.setAttribute("size","m"),s.setAttribute("clickable",""),e.icon&&t(e.icon)&&s.setAttribute("start-icon",e.icon),s.addEventListener("db-click",()=>this.select(e)),this.append(s)}}}customElements.get("db-chat-suggestions")||customElements.define("db-chat-suggestions",DbChatSuggestions);export{DbChatSuggestions};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import './db-avatar.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI 응답 대기(생성 중) 표시. 3점 바운스 인디케이터.
|
|
5
|
+
* 아바타는 `db-avatar` 를 재사용합니다.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <db-chat-typing name="Orion" label="생각 중…"></db-chat-typing>
|
|
9
|
+
*/
|
|
10
|
+
declare class DbChatTyping extends HTMLElement {
|
|
11
|
+
static get observedAttributes(): string[];
|
|
12
|
+
connectedCallback(): void;
|
|
13
|
+
attributeChangedCallback(): void;
|
|
14
|
+
private render;
|
|
15
|
+
}
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
'db-chat-typing': DbChatTyping;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { DbChatTyping };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./db-avatar.js";class DbChatTyping extends HTMLElement{static get observedAttributes(){return["name","avatar-src","avatar-initials","label"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}render(){this.className="designbase-wc-chat-typing",this.setAttribute("role","status"),this.setAttribute("aria-live","polite"),this.setAttribute("aria-label",this.getAttribute("label")||"AI가 응답을 생성하고 있습니다"),this.replaceChildren();const t=this.getAttribute("avatar-src"),e=this.getAttribute("avatar-initials"),a=document.createElement("div");a.className="designbase-wc-chat-typing__avatar";const s=document.createElement("db-avatar");s.setAttribute("size","s"),s.setAttribute("variant","primary"),t&&s.setAttribute("src",t),e&&s.setAttribute("initials",e),a.append(s);const i=document.createElement("div");i.className="designbase-wc-chat-typing__bubble";const n=this.getAttribute("label");if(n){const t=document.createElement("span");t.className="designbase-wc-chat-typing__label",t.textContent=n,i.append(t)}const r=document.createElement("span");r.className="designbase-wc-chat-typing__dots",r.setAttribute("aria-hidden","true");for(let t=0;t<3;t+=1){const t=document.createElement("span");t.className="designbase-wc-chat-typing__dot",r.append(t)}i.append(r),this.append(a,i)}}customElements.get("db-chat-typing")||customElements.define("db-chat-typing",DbChatTyping);export{DbChatTyping};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import './db-chat-orb.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 채팅 시작 전 화면의 히어로 블록.
|
|
5
|
+
* 오브(또는 로고 이미지) + 인사말 + 타이틀 + 서브타이틀을 렌더링합니다.
|
|
6
|
+
* 타이틀의 `highlight` 부분은 그라디언트로 강조됩니다.
|
|
7
|
+
*
|
|
8
|
+
* 컴포저(`db-chat-composer`)와 추천(`db-chat-suggestions`)은 형제 요소로 배치합니다.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* <db-chat-welcome
|
|
12
|
+
* greeting="안녕하세요"
|
|
13
|
+
* title="오늘은 무엇을 도와드릴까요?"
|
|
14
|
+
* highlight="도와드릴까요"
|
|
15
|
+
* orb-variant="aurora">
|
|
16
|
+
* </db-chat-welcome>
|
|
17
|
+
*/
|
|
18
|
+
declare class DbChatWelcome extends HTMLElement {
|
|
19
|
+
static get observedAttributes(): string[];
|
|
20
|
+
connectedCallback(): void;
|
|
21
|
+
attributeChangedCallback(): void;
|
|
22
|
+
private render;
|
|
23
|
+
/** 타이틀에서 highlight 구간만 그라디언트 span 으로 감싼다. */
|
|
24
|
+
private appendTitle;
|
|
25
|
+
}
|
|
26
|
+
declare global {
|
|
27
|
+
interface HTMLElementTagNameMap {
|
|
28
|
+
'db-chat-welcome': DbChatWelcome;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { DbChatWelcome };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./db-chat-orb.js";const e=["center","left"];class DbChatWelcome extends HTMLElement{static get observedAttributes(){return["greeting","title","highlight","subtitle","align","orb-variant","orb-state","orb-size","hide-orb","avatar-src"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}render(){const t=this.getAttribute("greeting")||"",a=this.getAttribute("title")||"",s=this.getAttribute("highlight")||"",c=this.getAttribute("subtitle")||"",i=e.includes(this.getAttribute("align"))?this.getAttribute("align"):"center",n=this.getAttribute("avatar-src"),r=this.hasAttribute("hide-orb");if(this.className=`designbase-wc-chat-welcome designbase-wc-chat-welcome--${i}`,this.replaceChildren(),n){const e=document.createElement("div");e.className="designbase-wc-chat-welcome__media";const t=document.createElement("img");t.className="designbase-wc-chat-welcome__logo",t.src=n,t.alt="",t.setAttribute("aria-hidden","true"),e.append(t),this.append(e)}else if(!r){const e=document.createElement("db-chat-orb");e.className="designbase-wc-chat-welcome__orb",e.setAttribute("size",this.getAttribute("orb-size")||"l"),e.setAttribute("variant",this.getAttribute("orb-variant")||"aurora"),e.setAttribute("state",this.getAttribute("orb-state")||"idle"),this.append(e)}const l=document.createElement("h1");if(l.className="designbase-wc-chat-welcome__heading",t){const e=document.createElement("span");e.className="designbase-wc-chat-welcome__greeting",e.textContent=t,l.append(e)}if(a){const e=document.createElement("span");e.className="designbase-wc-chat-welcome__title",this.appendTitle(e,a,s),l.append(e)}if(this.append(l),c){const e=document.createElement("p");e.className="designbase-wc-chat-welcome__subtitle",e.textContent=c,this.append(e)}}appendTitle(e,t,a){const s=a?t.indexOf(a):-1;if(-1===s)return void(e.textContent=t);const c=t.slice(0,s),i=t.slice(s+a.length);c&&e.append(document.createTextNode(c));const n=document.createElement("span");n.className="designbase-wc-chat-welcome__highlight",n.textContent=a,e.append(n),i&&e.append(document.createTextNode(i))}}customElements.get("db-chat-welcome")||customElements.define("db-chat-welcome",DbChatWelcome);export{DbChatWelcome};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import './db-chat-orb.js';
|
|
2
|
+
import './db-badge.js';
|
|
3
|
+
import './db-chat-welcome.js';
|
|
4
|
+
import './db-chat-composer.js';
|
|
5
|
+
import { DbChatSuggestion } from './db-chat-suggestions.js';
|
|
6
|
+
import './db-chat-message.js';
|
|
7
|
+
import './db-chat-typing.js';
|
|
8
|
+
import './db-avatar.js';
|
|
9
|
+
|
|
10
|
+
declare const VIEWS: readonly ["home", "messages", "conversation", "help"];
|
|
11
|
+
type ChatView = (typeof VIEWS)[number];
|
|
12
|
+
declare const POSITIONS: readonly ["bottom-right", "bottom-left"];
|
|
13
|
+
type ChatPosition = (typeof POSITIONS)[number];
|
|
14
|
+
interface DbChatWidgetMessage {
|
|
15
|
+
id?: string;
|
|
16
|
+
/** `assistant`(상담원/봇) 또는 `user` */
|
|
17
|
+
role?: 'assistant' | 'user';
|
|
18
|
+
text: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
time?: string;
|
|
21
|
+
avatarSrc?: string;
|
|
22
|
+
avatarInitials?: string;
|
|
23
|
+
/** 말풍선 액션 (예: 'copy,like,dislike') — db-chat-message 로 전달 */
|
|
24
|
+
actions?: string;
|
|
25
|
+
}
|
|
26
|
+
interface DbChatWidgetConversation {
|
|
27
|
+
id?: string;
|
|
28
|
+
name: string;
|
|
29
|
+
avatarSrc?: string;
|
|
30
|
+
avatarInitials?: string;
|
|
31
|
+
preview?: string;
|
|
32
|
+
time?: string;
|
|
33
|
+
unread?: number;
|
|
34
|
+
online?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* FAB 로 열고 닫는 우측 하단 채팅 상담 위젯(껍데기).
|
|
38
|
+
*
|
|
39
|
+
* 홈·메시지·대화·도움말 4개 화면을 하단 탭으로 전환하며, 본문은 AI 채팅 프리미티브
|
|
40
|
+
* (`db-chat-welcome` · `db-chat-message` · `db-chat-typing` · `db-chat-composer`
|
|
41
|
+
* · `db-chat-suggestions` · `db-chat-orb`)를 조합해 구성합니다. 실제 백엔드 연동
|
|
42
|
+
* 없이 UI 골격만 제공하고, 상호작용은 이벤트로 위임합니다.
|
|
43
|
+
*
|
|
44
|
+
* @fires db-open - 패널 열림
|
|
45
|
+
* @fires db-close - 패널 닫힘
|
|
46
|
+
* @fires db-toggle - FAB 토글 (detail: { open })
|
|
47
|
+
* @fires db-view-change - 화면 전환 (detail: { view })
|
|
48
|
+
* @fires db-send - 메시지 전송 (detail: { text })
|
|
49
|
+
* @fires db-menu-select - 홈 메뉴 선택 (detail: { id, value })
|
|
50
|
+
* @fires db-quick-reply - 빠른 답변 선택 (detail: { id, value })
|
|
51
|
+
* @fires db-article-select - 도움말 문서 선택 (detail: { id, title })
|
|
52
|
+
* @fires db-conversation-select - 대화 목록 선택 (detail: { id })
|
|
53
|
+
* @fires db-message-action - 말풍선 액션 (detail: { action, text })
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* const chat = document.createElement('db-chat-widget');
|
|
57
|
+
* chat.setAttribute('agent-name', '디지털 상담원');
|
|
58
|
+
* chat.setAttribute('unread', '2');
|
|
59
|
+
* document.body.appendChild(chat);
|
|
60
|
+
* chat.addEventListener('db-send', (e) => {
|
|
61
|
+
* chat.setTyping(true);
|
|
62
|
+
* setTimeout(() => {
|
|
63
|
+
* chat.setTyping(false);
|
|
64
|
+
* chat.addMessage({ role: 'assistant', text: '확인했어요!' });
|
|
65
|
+
* }, 900);
|
|
66
|
+
* });
|
|
67
|
+
*/
|
|
68
|
+
declare class DbChatWidget extends HTMLElement {
|
|
69
|
+
static get observedAttributes(): string[];
|
|
70
|
+
private built;
|
|
71
|
+
private typing;
|
|
72
|
+
private _messages;
|
|
73
|
+
private _conversations;
|
|
74
|
+
private _menuItems;
|
|
75
|
+
private _quickReplies;
|
|
76
|
+
private _articles;
|
|
77
|
+
private launcher;
|
|
78
|
+
private launcherBadge;
|
|
79
|
+
private panel;
|
|
80
|
+
private headerEl;
|
|
81
|
+
private bodyEl;
|
|
82
|
+
private footerEl;
|
|
83
|
+
private navEl;
|
|
84
|
+
private composer;
|
|
85
|
+
private threadEl;
|
|
86
|
+
get open(): boolean;
|
|
87
|
+
set open(v: boolean);
|
|
88
|
+
get view(): ChatView;
|
|
89
|
+
set view(v: ChatView);
|
|
90
|
+
get position(): ChatPosition;
|
|
91
|
+
set position(v: ChatPosition);
|
|
92
|
+
get unread(): number;
|
|
93
|
+
set unread(v: number);
|
|
94
|
+
set messages(v: DbChatWidgetMessage[]);
|
|
95
|
+
get messages(): DbChatWidgetMessage[];
|
|
96
|
+
set conversations(v: DbChatWidgetConversation[]);
|
|
97
|
+
get conversations(): DbChatWidgetConversation[];
|
|
98
|
+
set menuItems(v: DbChatSuggestion[]);
|
|
99
|
+
get menuItems(): DbChatSuggestion[];
|
|
100
|
+
set quickReplies(v: DbChatSuggestion[]);
|
|
101
|
+
get quickReplies(): DbChatSuggestion[];
|
|
102
|
+
set articles(v: DbChatSuggestion[]);
|
|
103
|
+
get articles(): DbChatSuggestion[];
|
|
104
|
+
connectedCallback(): void;
|
|
105
|
+
attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
|
|
106
|
+
private hydrateFromAttributes;
|
|
107
|
+
private build;
|
|
108
|
+
private sync;
|
|
109
|
+
private syncOpenState;
|
|
110
|
+
private renderBadge;
|
|
111
|
+
private renderHeader;
|
|
112
|
+
private iconButton;
|
|
113
|
+
private renderBody;
|
|
114
|
+
private renderHome;
|
|
115
|
+
private renderMessages;
|
|
116
|
+
private renderConversation;
|
|
117
|
+
private renderHelp;
|
|
118
|
+
private buildMessage;
|
|
119
|
+
private buildTyping;
|
|
120
|
+
private buildEmpty;
|
|
121
|
+
private renderFooter;
|
|
122
|
+
private renderNav;
|
|
123
|
+
private scrollThreadToEnd;
|
|
124
|
+
private nowLabel;
|
|
125
|
+
private dispatch;
|
|
126
|
+
openWidget(): void;
|
|
127
|
+
close(): void;
|
|
128
|
+
toggle(): void;
|
|
129
|
+
setView(view: ChatView): void;
|
|
130
|
+
/** 스레드에 메시지를 추가하고 대화 화면으로 전환/렌더 */
|
|
131
|
+
addMessage(msg: DbChatWidgetMessage): void;
|
|
132
|
+
/** 챗봇 입력중(...) 표시 토글 */
|
|
133
|
+
setTyping(on: boolean): void;
|
|
134
|
+
/** 전송 중(정지 버튼) 상태 — 컴포저에 위임 */
|
|
135
|
+
setStreaming(on: boolean): void;
|
|
136
|
+
/** 스레드 전체 교체 */
|
|
137
|
+
setMessages(messages: DbChatWidgetMessage[]): void;
|
|
138
|
+
}
|
|
139
|
+
declare global {
|
|
140
|
+
interface HTMLElementTagNameMap {
|
|
141
|
+
'db-chat-widget': DbChatWidget;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export { DbChatWidget };
|
|
146
|
+
export type { DbChatWidgetConversation, DbChatWidgetMessage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createDbIcon as t}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";const e=["home","messages","conversation","help"],i=["bottom-right","bottom-left"],s=[{id:"products",title:"제품·정책 알아보기",description:"AI가 정확하게 안내해 드려요",icon:"sparkle"},{id:"services",title:"상담원과 대화하기",description:"지금 바로 대화를 시작하세요",icon:"mail"},{id:"status",title:"주문·배송 조회",description:"진행 상황을 확인해 보세요",icon:"package"}],a=[{id:"products",title:"제품·정책 알아보기"},{id:"services",title:"서비스 상담하기"}],r=[{id:"m1",role:"assistant",name:"상담원",text:"안녕하세요. 무엇을 도와드릴까요? 제품과 정책을 24시간 정확하게 안내해 드릴게요.",time:"오후 12:31"},{id:"m2",role:"assistant",name:"상담원",text:"아래에서 원하시는 도움을 선택하시거나, 자유롭게 메시지를 입력해 주세요.",time:"오후 12:31",actions:"like,dislike"}],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일 전"}],o=[{id:"a1",title:"시작하기 — 5분 만에 설정 완료",description:"계정 생성부터 첫 배포까지",icon:"sparkle"},{id:"a2",title:"요금제와 결제 안내",description:"플랜 비교 · 청구 주기",icon:"star"},{id:"a3",title:"팀원 초대와 권한 관리",description:"역할별 접근 권한 설정",icon:"user"},{id:"a4",title:"데이터 내보내기·백업",description:"안전하게 보관하는 방법",icon:"download"}],h={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.createElement("template");return i.innerHTML=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="${e}" height="${e}" fill="none" aria-hidden="true" focusable="false">${h[t]}</svg>`,i.content.firstElementChild}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 l="designbase-wc-chat";class DbChatWidget extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.typing=!1,this._messages=r,this._conversations=n,this._menuItems=s,this._quickReplies=a,this._articles=o,this.composer=null,this.threadEl=null}static get observedAttributes(){return["open","view","position","agent-name","agent-avatar","agent-status","greeting","title","subtitle","placeholder","unread","accent","orb-variant","launcher-label","search-enabled"]}get open(){return this.hasAttribute("open")}set open(t){this.toggleAttribute("open",Boolean(t))}get view(){const t=this.getAttribute("view");return e.includes(t)?t:"home"}set view(t){this.setAttribute("view",t)}get position(){const t=this.getAttribute("position");return i.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&&this.renderBody()}get articles(){return this._articles}connectedCallback(){this.hydrateFromAttributes(),this.build(),this.sync()}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(l),this.replaceChildren(),this.launcher=el("button",`${l}__launcher`),this.launcher.type="button",this.launcher.setAttribute("aria-haspopup","dialog");const t=el("span",`${l}__launcher-icon ${l}__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",`${l}__launcher-icon ${l}__launcher-icon--close`);i.append(widgetIcon("close-x",24)),this.launcherBadge=document.createElement("db-badge"),this.launcherBadge.className=`${l}__launcher-badge`,this.launcherBadge.setAttribute("badge-style","number"),this.launcherBadge.setAttribute("variant","danger"),this.launcherBadge.setAttribute("size","s"),this.launcherBadge.setAttribute("max-count","99"),this.launcher.append(t,i,this.launcherBadge),this.launcher.addEventListener("click",()=>this.toggle()),this.panel=el("div",`${l}__panel`),this.panel.setAttribute("role","dialog"),this.panel.setAttribute("aria-modal","false"),this.headerEl=el("div",`${l}__header`),this.bodyEl=el("div",`${l}__body`),this.footerEl=el("div",`${l}__footer`),this.navEl=el("div",`${l}__nav`),this.panel.append(this.headerEl,this.bodyEl,this.footerEl,this.navEl),this.append(this.panel,this.launcher)}sync(){i.forEach(t=>this.classList.toggle(`${l}--${t}`,t===this.position)),e.forEach(t=>this.classList.toggle(`${l}--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(`${l}--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 e=this.view,i=this.getAttribute("agent-name")||"디지털 상담원",s=el("div",`${l}__header-bar`);if("conversation"===e){const e=this.iconButton(t("chevron-left",20),"뒤로",()=>this.setView("messages")),a=el("div",`${l}__header-identity`),r=el("db-chat-orb");r.className=`${l}__header-orb`,r.setAttribute("size","xs"),r.setAttribute("variant",this.getAttribute("orb-variant")||"violet");const n=el("div",`${l}__header-meta`);n.append(el("span",`${l}__header-name`,i),el("span",`${l}__header-status`,this.getAttribute("agent-status")||"보통 몇 분 내 응답")),a.append(r,n),s.append(e,a,this.iconButton(widgetIcon("close-x",20),"닫기",()=>this.close()))}else{const t="home"===e?"홈":"messages"===e?"메시지":"도움말";s.append(el("span",`${l}__header-title`,t),this.iconButton(widgetIcon("close-x",20),"닫기",()=>this.close()))}this.headerEl.append(s)}iconButton(t,e,i){const s=el("button",`${l}__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(`${l}__body--thread`,"conversation"===this.view),this.view){case"home":this.renderHome();break;case"messages":this.renderMessages();break;case"conversation":this.renderConversation();break;case"help":this.renderHelp()}}renderHome(){const e=el("db-chat-welcome",`${l}__welcome`);e.setAttribute("greeting",this.getAttribute("greeting")||"안녕하세요"),e.setAttribute("title",this.getAttribute("title")||"무엇을 도와드릴까요?"),e.setAttribute("highlight",this.getAttribute("highlight")||this.getAttribute("title")||"도와드릴까요"),e.setAttribute("subtitle",this.getAttribute("subtitle")||"제품·정책을 24시간 정확하게 안내해 드릴게요."),e.setAttribute("orb-variant",this.getAttribute("orb-variant")||"violet"),e.setAttribute("align","left"),e.setAttribute("orb-size","m"),this.bodyEl.append(e);const i=this._conversations[0];if(i){const e=el("button",`${l}__recent`);e.type="button";const s=el("db-avatar",`${l}__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("badge-style","number"),s.setAttribute("badge-variant","primary"),s.setAttribute("badge-max-count","99"));const a=el("div",`${l}__recent-body`);a.append(el("span",`${l}__recent-title`,"최근 대화 이어가기"),el("span",`${l}__recent-preview`,i.preview||"")),e.append(s,a,t("chevron-right",18)),e.addEventListener("click",()=>{this.dispatch("db-conversation-select",{id:i.id}),this.setView("conversation")}),this.bodyEl.append(e)}if(this._menuItems.length){const t=el("db-chat-suggestions",`${l}__menu`);t.setAttribute("layout","card"),t.setAttribute("columns","1"),t.items=this._menuItems,t.addEventListener("db-select",t=>{t.stopPropagation();const e=t.detail;this.dispatch("db-menu-select",e),this.setView("conversation")}),this.bodyEl.append(t)}if("false"!==this.getAttribute("search-enabled")){const e=el("button",`${l}__search-entry`);e.type="button",e.append(t("search",18),el("span",void 0,"도움말 검색")),e.addEventListener("click",()=>this.setView("help")),this.bodyEl.append(e)}}renderMessages(){if(!this._conversations.length)return void this.bodyEl.append(this.buildEmpty("진행 중인 대화가 없어요","새 대화를 시작해 보세요."));const t=el("div",`${l}__conv-list`);this._conversations.forEach(e=>{const i=el("button",`${l}__conv`);i.type="button";const s=el("db-avatar",`${l}__conv-avatar`);s.setAttribute("size","m"),s.setAttribute("variant","primary"),e.avatarSrc?s.setAttribute("src",e.avatarSrc):s.setAttribute("initials",e.avatarInitials||e.name.charAt(0)||"?"),e.online&&s.setAttribute("status","online");const a=el("div",`${l}__conv-body`),r=el("div",`${l}__conv-top`);if(r.append(el("span",`${l}__conv-name`,e.name)),e.time&&r.append(el("span",`${l}__conv-time`,e.time)),a.append(r,el("span",`${l}__conv-preview`,e.preview||"")),i.append(s,a),e.unread){const t=el("db-badge",`${l}__conv-unread`);t.setAttribute("badge-style","number"),t.setAttribute("variant","primary"),t.setAttribute("size","s"),t.setAttribute("max-count","99"),t.setAttribute("count",String(e.unread)),i.append(t)}i.addEventListener("click",()=>{this.dispatch("db-conversation-select",{id:e.id}),this.setView("conversation")}),t.append(i)}),this.bodyEl.append(t)}renderConversation(){const t=el("div",`${l}__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 e=el("div",`${l}__help-search`),i=el("span",`${l}__help-search-icon`);i.append(t("search",18));const s=el("input",`${l}__help-input`);if(s.type="search",s.placeholder="도움말 검색…",e.append(i,s),this.bodyEl.append(e),!this._articles.length)return void this.bodyEl.append(this.buildEmpty("문서가 없어요","검색어를 바꿔보세요."));const a=el("db-chat-suggestions",`${l}__articles`);a.setAttribute("layout","card"),a.setAttribute("columns","1"),a.items=this._articles,a.addEventListener("db-select",t=>{t.stopPropagation();const e=t.detail;this.dispatch("db-article-select",{id:e.id,title:e.title})}),this.bodyEl.append(a)}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,i.addEventListener("db-action",t=>{t.stopPropagation(),this.dispatch("db-message-action",t.detail)}),i}buildTyping(){const t=el("db-chat-typing",`${l}__typing`);return t.setAttribute("name",this.getAttribute("agent-name")||"상담원"),t.setAttribute("label","입력 중"),t}buildEmpty(t,e){const i=el("div",`${l}__empty`),s=el("db-chat-orb",`${l}__empty-orb`);return s.setAttribute("size","m"),s.setAttribute("variant",this.getAttribute("orb-variant")||"violet"),i.append(s,el("p",`${l}__empty-title`,t),el("p",`${l}__empty-desc`,e)),i}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",`${l}__composer`);t.setAttribute("placeholder",this.getAttribute("placeholder")||"메시지를 입력하세요…"),t.setAttribute("attach",""),t.setAttribute("voice",""),t.addEventListener("db-send",e=>{e.stopPropagation();const i=e.detail?.value?.trim();i&&(t.clear(),this.dispatch("db-send",{text:i}),this.addMessage({role:"user",text:i,time:this.nowLabel()}))}),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 t="conversation"!==this.view;if(this.navEl.hidden=!t,!t)return;[{view:"home",icon:"home",label:"홈"},{view:"messages",icon:"chat",label:"메시지"},{view:"help",icon:"help",label:"도움말"}].forEach(t=>{const e=el("button",`${l}__tab`);e.type="button";const i=this.view===t.view;if(e.classList.toggle(`${l}__tab--active`,i),e.setAttribute("aria-current",String(i)),e.append(widgetIcon(t.icon,22),el("span",`${l}__tab-label`,t.label)),"messages"===t.view&&this.unread>0){const t=el("db-badge",`${l}__tab-badge`);t.setAttribute("badge-style","dot"),t.setAttribute("variant","danger"),t.setAttribute("size","s"),e.append(t)}e.addEventListener("click",()=>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 +1 @@
|
|
|
1
|
-
import{createDbIcon as
|
|
1
|
+
import{isDbIconName as e,createDbIcon as t}from"./db-icons.js";const i=["s","m","l"],s=["default","primary","success","warning","danger","info","neutral"];class DbChip extends HTMLElement{static get observedAttributes(){return["label","size","variant","deletable","disabled","selected","full-width","clickable","start-icon","end-icon"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}iconSize(e){return"s"===e?12:"l"===e?20:16}render(){const n=this.getAttribute("label")||this.textContent?.trim()||"",c=this.getAttribute("size")||"m",a=this.getAttribute("variant")||"default",d=this.hasAttribute("disabled"),l=this.hasAttribute("deletable"),b=this.hasAttribute("selected"),h=this.hasAttribute("full-width"),r=this.hasAttribute("clickable"),o=this.getAttribute("start-icon"),p=this.getAttribute("end-icon"),u=i.includes(c)?c:"m",m=s.includes(a)?a:"default",g=this.iconSize(u);if(this.className=["designbase-wc-chip",`designbase-wc-chip--${u}`,`designbase-wc-chip--${m}`,l&&"designbase-wc-chip--deletable",d&&"designbase-wc-chip--disabled",b&&"designbase-wc-chip--selected",r&&"designbase-wc-chip--clickable",h&&"designbase-wc-chip--full-width"].filter(Boolean).join(" "),this.replaceChildren(),r&&!d?(this.setAttribute("role","button"),this.tabIndex=0):(this.removeAttribute("role"),this.removeAttribute("tabindex")),o&&e(o)){const e=document.createElement("span");e.className="designbase-wc-chip__start-icon",e.append(t(o,g)),this.append(e)}const w=document.createElement("span");if(w.className="designbase-wc-chip__label",w.textContent=n,this.append(w),p&&e(p)&&!l){const e=document.createElement("span");e.className="designbase-wc-chip__end-icon",e.append(t(p,g)),this.append(e)}if(l){const e=document.createElement("button");e.type="button",e.className="designbase-wc-chip__delete-button",e.disabled=d,e.setAttribute("aria-label",`${n} 삭제`),e.append(t("close",g)),e.addEventListener("click",e=>{e.stopPropagation(),d||this.dispatchEvent(new CustomEvent("db-delete",{bubbles:!0,composed:!0}))}),this.append(e)}this.onclick=()=>{!d&&r&&this.dispatchEvent(new CustomEvent("db-click",{bubbles:!0,composed:!0}))},this.onkeydown=e=>{"Enter"!==e.key&&" "!==e.key||d||!r||(e.preventDefault(),this.dispatchEvent(new CustomEvent("db-click",{bubbles:!0,composed:!0})))}}}customElements.get("db-chip")||customElements.define("db-chip",DbChip);export{DbChip};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import './db-progressbar.js';
|
|
2
|
+
import './db-button.js';
|
|
3
|
+
|
|
4
|
+
declare const STATUSES: readonly ["downloading", "paused", "error", "complete"];
|
|
5
|
+
type DownloadStatus = (typeof STATUSES)[number];
|
|
6
|
+
declare const POSITIONS: readonly ["bottom-right", "bottom-left", "bottom-center", "top-right", "top-left", "top-center"];
|
|
7
|
+
type DownloadPosition = (typeof POSITIONS)[number];
|
|
8
|
+
/**
|
|
9
|
+
* 우측 하단(기본)에 떠 있는 다운로드 진행률 위젯.
|
|
10
|
+
*
|
|
11
|
+
* 진행 바는 `<db-progressbar>`, 액션 버튼은 `<db-button>` 을 그대로 재사용합니다.
|
|
12
|
+
* 같은 위치에 여러 개가 뜨면 `stack-mode`에 따라 위로 쌓이거나(deck) 세로로 쌓입니다.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const dl = document.createElement('db-download-progress');
|
|
16
|
+
* dl.setAttribute('file-name', 'ui-kit-icons.figma');
|
|
17
|
+
* dl.setAttribute('value', '43');
|
|
18
|
+
* dl.setAttribute('auto-remove', '');
|
|
19
|
+
* document.body.appendChild(dl);
|
|
20
|
+
* dl.setProgress(80);
|
|
21
|
+
* dl.setStatus('complete');
|
|
22
|
+
*/
|
|
23
|
+
declare class DbDownloadProgress extends HTMLElement {
|
|
24
|
+
static get observedAttributes(): string[];
|
|
25
|
+
private built;
|
|
26
|
+
private renderedStatus;
|
|
27
|
+
private closeTimerId;
|
|
28
|
+
private resizeObserver;
|
|
29
|
+
private registeredPosition;
|
|
30
|
+
closed: boolean;
|
|
31
|
+
private iconEl;
|
|
32
|
+
private labelEl;
|
|
33
|
+
private fileEl;
|
|
34
|
+
private subtitleEl;
|
|
35
|
+
private inlinePercentEl;
|
|
36
|
+
private percentEl;
|
|
37
|
+
private remainingEl;
|
|
38
|
+
private metaDotEl;
|
|
39
|
+
private toggleBtn;
|
|
40
|
+
private closeBtn;
|
|
41
|
+
private actionsEl;
|
|
42
|
+
private mainRow;
|
|
43
|
+
private bodyBar;
|
|
44
|
+
private edgeBar;
|
|
45
|
+
get value(): number;
|
|
46
|
+
set value(v: number);
|
|
47
|
+
get max(): number;
|
|
48
|
+
set max(v: number);
|
|
49
|
+
get status(): DownloadStatus;
|
|
50
|
+
set status(v: DownloadStatus);
|
|
51
|
+
get fileName(): string;
|
|
52
|
+
set fileName(v: string);
|
|
53
|
+
get position(): DownloadPosition;
|
|
54
|
+
set position(v: DownloadPosition);
|
|
55
|
+
get minimized(): boolean;
|
|
56
|
+
set minimized(v: boolean);
|
|
57
|
+
/** `stack` = 세로로 위로 쌓기(기본), `deck` = 스케일·z-index 카드 덱 */
|
|
58
|
+
get stackMode(): 'stack' | 'deck';
|
|
59
|
+
set stackMode(v: 'stack' | 'deck');
|
|
60
|
+
connectedCallback(): void;
|
|
61
|
+
disconnectedCallback(): void;
|
|
62
|
+
attributeChangedCallback(name: string): void;
|
|
63
|
+
private registerStack;
|
|
64
|
+
private unregisterStack;
|
|
65
|
+
private build;
|
|
66
|
+
private sync;
|
|
67
|
+
private renderActions;
|
|
68
|
+
private handleMainClick;
|
|
69
|
+
private handleToggleClick;
|
|
70
|
+
private handleCloseClick;
|
|
71
|
+
minimize(): void;
|
|
72
|
+
expand(): void;
|
|
73
|
+
toggle(): void;
|
|
74
|
+
setProgress(value: number): void;
|
|
75
|
+
setStatus(status: DownloadStatus): void;
|
|
76
|
+
/**
|
|
77
|
+
* 닫기(슬라이드 아웃).
|
|
78
|
+
* React 등 호스트가 DOM을 소유할 때는 `auto-remove` 없이 `db-close`만 받고 언마운트하세요.
|
|
79
|
+
* body에 직접 append한 경우 `auto-remove`를 주면 DOM에서 제거됩니다.
|
|
80
|
+
*/
|
|
81
|
+
close(): void;
|
|
82
|
+
}
|
|
83
|
+
declare global {
|
|
84
|
+
interface HTMLElementTagNameMap {
|
|
85
|
+
'db-download-progress': DbDownloadProgress;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { DbDownloadProgress };
|