@designbasekorea/ui-wc 0.5.3 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/chunks/lottie-e273b936.js +17032 -0
  2. package/dist/esm/components/animation-background/canvas-controller.js +121 -1
  3. package/dist/esm/components/animation-background/controls.js +180 -1
  4. package/dist/esm/components/animation-background/graphite-controller.js +196 -1
  5. package/dist/esm/components/animation-background/graphite-presets.js +42 -1
  6. package/dist/esm/components/animation-background/graphite-shader.js +111 -1
  7. package/dist/esm/components/animation-background/layers.js +369 -1
  8. package/dist/esm/components/animation-background/lumina-presets.js +53 -1
  9. package/dist/esm/components/animation-background/lumina-shaders.js +181 -1
  10. package/dist/esm/components/animation-background/lumina-utils.js +60 -1
  11. package/dist/esm/components/animation-background/particle-defaults.js +36 -1
  12. package/dist/esm/components/animation-background/renderers.js +167 -1
  13. package/dist/esm/components/animation-background/shader-controller.js +154 -1
  14. package/dist/esm/components/color-picker-utils.js +134 -1
  15. package/dist/esm/components/date-picker-utils.js +140 -1
  16. package/dist/esm/components/db-accordion.js +304 -1
  17. package/dist/esm/components/db-ad-banner.js +455 -1
  18. package/dist/esm/components/db-alert.js +149 -1
  19. package/dist/esm/components/db-animation-background.js +486 -1
  20. package/dist/esm/components/db-animation-text.js +601 -1
  21. package/dist/esm/components/db-app-badge.js +88 -1
  22. package/dist/esm/components/db-audio-player.js +752 -1
  23. package/dist/esm/components/db-avatar.js +188 -1
  24. package/dist/esm/components/db-backdrop.js +139 -1
  25. package/dist/esm/components/db-badge.js +91 -1
  26. package/dist/esm/components/db-banner.js +188 -1
  27. package/dist/esm/components/db-bottom-navigation.js +146 -1
  28. package/dist/esm/components/db-bottom-sheet.js +481 -1
  29. package/dist/esm/components/db-breadcrumbs.js +329 -1
  30. package/dist/esm/components/db-button.js +316 -1
  31. package/dist/esm/components/db-calendar.js +675 -1
  32. package/dist/esm/components/db-card.js +265 -1
  33. package/dist/esm/components/db-carousel.js +264 -1
  34. package/dist/esm/components/db-checkbox.js +202 -1
  35. package/dist/esm/components/db-chip.js +104 -1
  36. package/dist/esm/components/db-code-block.js +304 -1
  37. package/dist/esm/components/db-color-picker.js +746 -1
  38. package/dist/esm/components/db-confirm.js +191 -1
  39. package/dist/esm/components/db-container.js +39 -1
  40. package/dist/esm/components/db-context-menu.js +265 -1
  41. package/dist/esm/components/db-countdown.js +146 -1
  42. package/dist/esm/components/db-cursor-follower.js +173 -1
  43. package/dist/esm/components/db-date-picker.js +837 -1
  44. package/dist/esm/components/db-divider.js +66 -1
  45. package/dist/esm/components/db-drawer.js +245 -1
  46. package/dist/esm/components/db-dropdown.js +493 -1
  47. package/dist/esm/components/db-dropzone.js +266 -1
  48. package/dist/esm/components/db-empty-state.js +97 -1
  49. package/dist/esm/components/db-file-uploader.js +256 -1
  50. package/dist/esm/components/db-floating-action-button.js +139 -1
  51. package/dist/esm/components/db-footer.js +385 -1
  52. package/dist/esm/components/db-form.js +566 -1
  53. package/dist/esm/components/db-gantt.js +882 -1
  54. package/dist/esm/components/db-gradient.js +133 -1
  55. package/dist/esm/components/db-grid-background.js +909 -1
  56. package/dist/esm/components/db-grid.js +73 -1
  57. package/dist/esm/components/db-icons.js +82 -1
  58. package/dist/esm/components/db-image-list.js +214 -1
  59. package/dist/esm/components/db-image-placeholder.js +96 -1
  60. package/dist/esm/components/db-image.js +289 -1
  61. package/dist/esm/components/db-indicator.js +517 -1
  62. package/dist/esm/components/db-input.js +420 -1
  63. package/dist/esm/components/db-kanban.js +538 -1
  64. package/dist/esm/components/db-label.js +66 -1
  65. package/dist/esm/components/db-lightbox.js +372 -1
  66. package/dist/esm/components/db-list.js +340 -1
  67. package/dist/esm/components/db-logo-svg.js +72 -1
  68. package/dist/esm/components/db-logo.js +106 -1
  69. package/dist/esm/components/db-lottie.js +109 -1
  70. package/dist/esm/components/db-markdown-editor.js +458 -1
  71. package/dist/esm/components/db-marquee.js +402 -1
  72. package/dist/esm/components/db-masonry.js +94 -1
  73. package/dist/esm/components/db-menu-item.js +211 -1
  74. package/dist/esm/components/db-modal.js +522 -1
  75. package/dist/esm/components/db-navbar.js +680 -1
  76. package/dist/esm/components/db-onboarding-modal.js +322 -1
  77. package/dist/esm/components/db-page-header.js +312 -1
  78. package/dist/esm/components/db-pagination.js +271 -1
  79. package/dist/esm/components/db-popover.js +580 -1
  80. package/dist/esm/components/db-progress-step.js +166 -1
  81. package/dist/esm/components/db-progress.js +171 -1
  82. package/dist/esm/components/db-progressbar.js +72 -1
  83. package/dist/esm/components/db-radio.js +172 -1
  84. package/dist/esm/components/db-random-gradient.js +121 -1
  85. package/dist/esm/components/db-range-slider.js +811 -1
  86. package/dist/esm/components/db-rating.js +325 -1
  87. package/dist/esm/components/db-reorder.js +286 -1
  88. package/dist/esm/components/db-resizable-panels.js +282 -1
  89. package/dist/esm/components/db-scroll-area.js +126 -1
  90. package/dist/esm/components/db-search-bar.js +668 -1
  91. package/dist/esm/components/db-section-app-download.js +101 -1
  92. package/dist/esm/components/db-section-contact.js +102 -1
  93. package/dist/esm/components/db-section-cta.js +150 -1
  94. package/dist/esm/components/db-section-faq.js +120 -1
  95. package/dist/esm/components/db-section-feature-grid.js +137 -1
  96. package/dist/esm/components/db-section-feature.js +179 -1
  97. package/dist/esm/components/db-section-hero.js +243 -1
  98. package/dist/esm/components/db-section-logo-cloud.js +138 -1
  99. package/dist/esm/components/db-section-pricing.js +166 -1
  100. package/dist/esm/components/db-section-stats.js +129 -1
  101. package/dist/esm/components/db-section-testimonials.js +252 -1
  102. package/dist/esm/components/db-section.js +385 -1
  103. package/dist/esm/components/db-segment-control.js +231 -1
  104. package/dist/esm/components/db-select.js +571 -1
  105. package/dist/esm/components/db-share-icons.js +40 -1
  106. package/dist/esm/components/db-share.js +489 -1
  107. package/dist/esm/components/db-sidebar.js +1068 -1
  108. package/dist/esm/components/db-skeleton.js +94 -1
  109. package/dist/esm/components/db-spinner.js +102 -1
  110. package/dist/esm/components/db-split-view.js +297 -1
  111. package/dist/esm/components/db-stack.js +46 -1
  112. package/dist/esm/components/db-stat.js +218 -1
  113. package/dist/esm/components/db-stepper.js +398 -1
  114. package/dist/esm/components/db-table.js +412 -1
  115. package/dist/esm/components/db-tabs.js +335 -1
  116. package/dist/esm/components/db-testimonial.js +162 -1
  117. package/dist/esm/components/db-textarea.js +233 -1
  118. package/dist/esm/components/db-time-picker.js +523 -1
  119. package/dist/esm/components/db-timeline.js +174 -1
  120. package/dist/esm/components/db-toast.js +158 -1
  121. package/dist/esm/components/db-toggle.js +162 -1
  122. package/dist/esm/components/db-toolbar.js +94 -1
  123. package/dist/esm/components/db-tooltip.js +440 -1
  124. package/dist/esm/components/db-top-banner.js +121 -1
  125. package/dist/esm/components/db-tutorial.js +324 -1
  126. package/dist/esm/components/db-video-player.js +503 -1
  127. package/dist/esm/components/db-youtube-player.js +194 -1
  128. package/dist/esm/components/highlight-code.js +65 -1
  129. package/dist/esm/components/input-validation.js +50 -1
  130. package/dist/esm/components/marketing-block-host-utils.js +9 -1
  131. package/dist/esm/components/number-scrub.js +117 -1
  132. package/dist/esm/components/random-gradient-palettes.js +67 -1
  133. package/dist/esm/components/section-block-header-utils.js +48 -1
  134. package/dist/esm/helpers/confirm.js +52 -1
  135. package/dist/esm/helpers/toast.js +73 -1
  136. package/dist/esm/index.js +111 -1
  137. package/dist/esm/node_modules/prismjs/components/prism-bash.js +235 -1
  138. package/dist/esm/node_modules/prismjs/components/prism-clike.js +31 -1
  139. package/dist/esm/node_modules/prismjs/components/prism-css.js +64 -1
  140. package/dist/esm/node_modules/prismjs/components/prism-javascript.js +172 -1
  141. package/dist/esm/node_modules/prismjs/components/prism-json.js +27 -1
  142. package/dist/esm/node_modules/prismjs/components/prism-jsx.js +143 -1
  143. package/dist/esm/node_modules/prismjs/components/prism-markup.js +186 -1
  144. package/dist/esm/node_modules/prismjs/components/prism-scss.js +81 -1
  145. package/dist/esm/node_modules/prismjs/components/prism-tsx.js +15 -1
  146. package/dist/esm/node_modules/prismjs/components/prism-typescript.js +60 -1
  147. package/dist/index.cjs +55414 -1
  148. package/dist/index.css +1 -1
  149. package/dist/index.esm.js +38284 -1
  150. package/dist/react/chunks/lottie-72a4c6d4.js +17032 -0
  151. package/dist/react/index.cjs +56503 -1
  152. package/dist/react/index.d.ts +1 -1
  153. package/dist/react/index.esm.js +39373 -1
  154. package/package.json +1 -1
  155. package/dist/chunks/lottie-2d1f6278.js +0 -1
  156. package/dist/react/chunks/lottie-14e7addb.js +0 -1
@@ -1 +1,218 @@
1
- import{createDbIcon as t}from"./db-icons.js";const e=["s","m","l"],s=["default","minimal","card","colored","iconBox"],i=["horizontal","vertical","reverse"],a=["primary","secondary","success","warning","error","info","custom"],n={s:16,m:20,l:24};class DbStat extends HTMLElement{constructor(){super(...arguments),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["value","label","icon","icon-position","size","variant","layout","color","custom-color","progress","show-progress","change","show-change","description","show-description","clickable","disabled","animated"]}connectedCallback(){this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeyDown),this.render()}disconnectedCallback(){this.removeEventListener("click",this.handleClick),this.removeEventListener("keydown",this.handleKeyDown)}attributeChangedCallback(){this.render()}parseChange(){const t=this.getAttribute("change");if(!t)return null;try{const e=JSON.parse(t);if(e&&"number"==typeof e.value&&e.type)return e}catch{}return null}getChangeText(t){const e="increase"===t.type?"+":"-",s=t.period?` ${t.period}`:"";return`${e}${Math.abs(t.value)}%${s}`}resolveIconName(t){if(!t)return;return t in{user:1,sparkle:1,award:1,bulb:1,edit:1,search:1}?t:void 0}getProgressVariant(t){return"primary"===t?"primary":"success"===t?"success":"warning"===t?"warning":"error"===t?"danger":"default"}render(){const r=this.getAttribute("value")??"",c=this.getAttribute("label")||"",o=this.getAttribute("icon")||"",d=this.getAttribute("icon-position")||"left",l=this.getAttribute("size")||"m",h=e.includes(l)?l:"m",b=this.getAttribute("variant")||"default",u=s.includes(b)?b:"default",g=this.getAttribute("layout")||"horizontal",m=i.includes(g)?g:"horizontal",p=this.getAttribute("color")||"primary",w=a.includes(p)?p:"primary",v=this.getAttribute("custom-color")||"",A=this.getAttribute("progress"),y=null!==A?Number(A):NaN,_=this.hasAttribute("show-progress"),k=this.parseChange(),E=this.hasAttribute("show-change"),f=this.getAttribute("description")||"",N=this.hasAttribute("show-description"),C=this.hasAttribute("clickable"),x=this.hasAttribute("disabled"),$=this.hasAttribute("animated");this.className=["designbase-wc-stat",`designbase-wc-stat--size-${h}`,`designbase-wc-stat--variant-${u}`,`designbase-wc-stat--layout-${m}`,`designbase-wc-stat--color-${w}`,C&&"designbase-wc-stat--clickable",x&&"designbase-wc-stat--disabled",$&&"designbase-wc-stat--animated",_&&"designbase-wc-stat--with-progress",E&&k&&"designbase-wc-stat--with-change",N&&f&&"designbase-wc-stat--with-description"].filter(Boolean).join(" "),v?this.style.setProperty("--stat-color",v):this.style.removeProperty("--stat-color"),C&&!x?(this.setAttribute("role","button"),this.setAttribute("tabindex","0")):(this.removeAttribute("role"),this.removeAttribute("tabindex")),this.replaceChildren();const z=document.createElement("div");z.className="designbase-wc-stat__body";const D=this.resolveIconName(o);if(D){const e=document.createElement("div");e.className=`designbase-wc-stat__icon designbase-wc-stat__icon--${d}`;const s="iconBox"===u?n[h]:20;e.append(t(D,s)),z.append(e)}const S=document.createElement("div");S.className="designbase-wc-stat__content";const L=document.createElement("div");L.className="designbase-wc-stat__main";const M=document.createElement("div");M.className="designbase-wc-stat__label",M.textContent=c;const P=document.createElement("div");if(P.className="designbase-wc-stat__value",P.textContent=String(r),L.append(M,P),S.append(L),E&&k){const t=document.createElement("div");t.className=["designbase-wc-stat__change","increase"===k.type?"designbase-wc-stat__change--increase":"designbase-wc-stat__change--decrease"].join(" "),t.textContent=this.getChangeText(k),S.append(t)}if(N&&f){const t=document.createElement("div");t.className="designbase-wc-stat__description",t.textContent=f,S.append(t)}if(z.append(S),this.append(z),_&&!Number.isNaN(y)){const t=Math.min(Math.max(y,0),100),e=document.createElement("div");e.className="designbase-wc-stat__progress";const s=document.createElement("db-progressbar");s.setAttribute("value",String(t)),s.setAttribute("size","s"),s.setAttribute("variant",this.getProgressVariant(w)),s.setAttribute("full-width",""),e.append(s),this.append(e)}}}customElements.get("db-stat")||customElements.define("db-stat",DbStat);export{DbStat};
1
+ import { createDbIcon } from './db-icons.js';
2
+
3
+ const SIZES = ['s', 'm', 'l'];
4
+ const VARIANTS = ['default', 'minimal', 'card', 'colored', 'iconBox'];
5
+ const LAYOUTS = ['horizontal', 'vertical', 'reverse'];
6
+ const COLORS = ['primary', 'secondary', 'success', 'warning', 'error', 'info', 'custom'];
7
+ const ICON_BOX_GLYPH_PX = {
8
+ s: 16,
9
+ m: 20,
10
+ l: 24,
11
+ };
12
+ class DbStat extends HTMLElement {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.handleClick = () => {
16
+ if (this.hasAttribute('disabled') || !this.hasAttribute('clickable'))
17
+ return;
18
+ this.dispatchEvent(new CustomEvent('db-click', { bubbles: true, composed: true }));
19
+ };
20
+ this.handleKeyDown = (e) => {
21
+ if (!this.hasAttribute('clickable') || this.hasAttribute('disabled'))
22
+ return;
23
+ if (e.key !== 'Enter' && e.key !== ' ')
24
+ return;
25
+ e.preventDefault();
26
+ this.handleClick();
27
+ };
28
+ }
29
+ static get observedAttributes() {
30
+ return [
31
+ 'value',
32
+ 'label',
33
+ 'icon',
34
+ 'icon-position',
35
+ 'size',
36
+ 'variant',
37
+ 'layout',
38
+ 'color',
39
+ 'custom-color',
40
+ 'progress',
41
+ 'show-progress',
42
+ 'change',
43
+ 'show-change',
44
+ 'description',
45
+ 'show-description',
46
+ 'clickable',
47
+ 'disabled',
48
+ 'animated',
49
+ ];
50
+ }
51
+ connectedCallback() {
52
+ this.addEventListener('click', this.handleClick);
53
+ this.addEventListener('keydown', this.handleKeyDown);
54
+ this.render();
55
+ }
56
+ disconnectedCallback() {
57
+ this.removeEventListener('click', this.handleClick);
58
+ this.removeEventListener('keydown', this.handleKeyDown);
59
+ }
60
+ attributeChangedCallback() {
61
+ this.render();
62
+ }
63
+ parseChange() {
64
+ const raw = this.getAttribute('change');
65
+ if (!raw)
66
+ return null;
67
+ try {
68
+ const parsed = JSON.parse(raw);
69
+ if (parsed && typeof parsed.value === 'number' && parsed.type)
70
+ return parsed;
71
+ }
72
+ catch {
73
+ // ignore
74
+ }
75
+ return null;
76
+ }
77
+ getChangeText(change) {
78
+ const sign = change.type === 'increase' ? '+' : '-';
79
+ const period = change.period ? ` ${change.period}` : '';
80
+ return `${sign}${Math.abs(change.value)}%${period}`;
81
+ }
82
+ resolveIconName(icon) {
83
+ if (!icon)
84
+ return undefined;
85
+ const icons = {
86
+ user: 1,
87
+ sparkle: 1,
88
+ award: 1,
89
+ bulb: 1,
90
+ edit: 1,
91
+ search: 1,
92
+ };
93
+ return icon in icons ? icon : undefined;
94
+ }
95
+ getProgressVariant(color) {
96
+ if (color === 'primary')
97
+ return 'primary';
98
+ if (color === 'success')
99
+ return 'success';
100
+ if (color === 'warning')
101
+ return 'warning';
102
+ if (color === 'error')
103
+ return 'danger';
104
+ return 'default';
105
+ }
106
+ render() {
107
+ const value = this.getAttribute('value') ?? '';
108
+ const label = this.getAttribute('label') || '';
109
+ const icon = this.getAttribute('icon') || '';
110
+ const iconPosition = this.getAttribute('icon-position') || 'left';
111
+ const rawSize = this.getAttribute('size') || 'm';
112
+ const size = SIZES.includes(rawSize) ? rawSize : 'm';
113
+ const rawVariant = this.getAttribute('variant') || 'default';
114
+ const variant = VARIANTS.includes(rawVariant) ? rawVariant : 'default';
115
+ const rawLayout = this.getAttribute('layout') || 'horizontal';
116
+ const layout = LAYOUTS.includes(rawLayout) ? rawLayout : 'horizontal';
117
+ const rawColor = this.getAttribute('color') || 'primary';
118
+ const color = COLORS.includes(rawColor) ? rawColor : 'primary';
119
+ const customColor = this.getAttribute('custom-color') || '';
120
+ const progressRaw = this.getAttribute('progress');
121
+ const progress = progressRaw !== null ? Number(progressRaw) : NaN;
122
+ const showProgress = this.hasAttribute('show-progress');
123
+ const change = this.parseChange();
124
+ const showChange = this.hasAttribute('show-change');
125
+ const description = this.getAttribute('description') || '';
126
+ const showDescription = this.hasAttribute('show-description');
127
+ const clickable = this.hasAttribute('clickable');
128
+ const disabled = this.hasAttribute('disabled');
129
+ const animated = this.hasAttribute('animated');
130
+ this.className = [
131
+ 'designbase-wc-stat',
132
+ `designbase-wc-stat--size-${size}`,
133
+ `designbase-wc-stat--variant-${variant}`,
134
+ `designbase-wc-stat--layout-${layout}`,
135
+ `designbase-wc-stat--color-${color}`,
136
+ clickable && 'designbase-wc-stat--clickable',
137
+ disabled && 'designbase-wc-stat--disabled',
138
+ animated && 'designbase-wc-stat--animated',
139
+ showProgress && 'designbase-wc-stat--with-progress',
140
+ showChange && change && 'designbase-wc-stat--with-change',
141
+ showDescription && description && 'designbase-wc-stat--with-description',
142
+ ]
143
+ .filter(Boolean)
144
+ .join(' ');
145
+ if (customColor) {
146
+ this.style.setProperty('--stat-color', customColor);
147
+ }
148
+ else {
149
+ this.style.removeProperty('--stat-color');
150
+ }
151
+ if (clickable && !disabled) {
152
+ this.setAttribute('role', 'button');
153
+ this.setAttribute('tabindex', '0');
154
+ }
155
+ else {
156
+ this.removeAttribute('role');
157
+ this.removeAttribute('tabindex');
158
+ }
159
+ this.replaceChildren();
160
+ const body = document.createElement('div');
161
+ body.className = 'designbase-wc-stat__body';
162
+ const iconName = this.resolveIconName(icon);
163
+ if (iconName) {
164
+ const iconEl = document.createElement('div');
165
+ iconEl.className = `designbase-wc-stat__icon designbase-wc-stat__icon--${iconPosition}`;
166
+ const glyphSize = variant === 'iconBox' ? ICON_BOX_GLYPH_PX[size] : 20;
167
+ iconEl.append(createDbIcon(iconName, glyphSize));
168
+ body.append(iconEl);
169
+ }
170
+ const content = document.createElement('div');
171
+ content.className = 'designbase-wc-stat__content';
172
+ const main = document.createElement('div');
173
+ main.className = 'designbase-wc-stat__main';
174
+ const labelEl = document.createElement('div');
175
+ labelEl.className = 'designbase-wc-stat__label';
176
+ labelEl.textContent = label;
177
+ const valueEl = document.createElement('div');
178
+ valueEl.className = 'designbase-wc-stat__value';
179
+ valueEl.textContent = String(value);
180
+ main.append(labelEl, valueEl);
181
+ content.append(main);
182
+ if (showChange && change) {
183
+ const changeEl = document.createElement('div');
184
+ changeEl.className = [
185
+ 'designbase-wc-stat__change',
186
+ change.type === 'increase'
187
+ ? 'designbase-wc-stat__change--increase'
188
+ : 'designbase-wc-stat__change--decrease',
189
+ ].join(' ');
190
+ changeEl.textContent = this.getChangeText(change);
191
+ content.append(changeEl);
192
+ }
193
+ if (showDescription && description) {
194
+ const descEl = document.createElement('div');
195
+ descEl.className = 'designbase-wc-stat__description';
196
+ descEl.textContent = description;
197
+ content.append(descEl);
198
+ }
199
+ body.append(content);
200
+ this.append(body);
201
+ if (showProgress && !Number.isNaN(progress)) {
202
+ const clamped = Math.min(Math.max(progress, 0), 100);
203
+ const progressWrap = document.createElement('div');
204
+ progressWrap.className = 'designbase-wc-stat__progress';
205
+ const bar = document.createElement('db-progressbar');
206
+ bar.setAttribute('value', String(clamped));
207
+ bar.setAttribute('size', 's');
208
+ bar.setAttribute('variant', this.getProgressVariant(color));
209
+ bar.setAttribute('full-width', '');
210
+ progressWrap.append(bar);
211
+ this.append(progressWrap);
212
+ }
213
+ }
214
+ }
215
+ if (!customElements.get('db-stat'))
216
+ customElements.define('db-stat', DbStat);
217
+
218
+ export { DbStat };
@@ -1 +1,398 @@
1
- const t="designbase-wc-stepper",e=["s","m","l"],i={display:"block",width:"100%",minWidth:"0",height:"auto",padding:"0",margin:"0",border:"0",background:"transparent",color:"inherit",fontFamily:"inherit",fontWeight:"600",lineHeight:"1",textAlign:"center",outline:"none",boxShadow:"none",boxSizing:"border-box",appearance:"none"};class DbStepper extends HTMLElement{constructor(){super(...arguments),this.decrementBtn=null,this.incrementBtn=null,this.valueWrapEl=null,this.valueTextEl=null,this.valueInputEl=null,this.descriptionEl=null,this.boundInputKeyDown=t=>this.handleInputKeyDown(t),this.boundInputInput=()=>this.handleInputInput(),this.boundInputPaste=t=>this.handleInputPaste(t),this.boundInputBlur=()=>this.commitInputValue()}static get observedAttributes(){return["value","min","max","step","size","editable","full-width","disabled","readonly"]}connectedCallback(){this.querySelector(`.${t}__button`)||this.build(),this.syncFromAttributes()}attributeChangedCallback(t){this.decrementBtn&&this.syncFromAttributes()}get value(){return Number(this.getAttribute("value")||0)}set value(t){this.setAttribute("value",String(t))}build(){this.decrementBtn=document.createElement("button"),this.decrementBtn.type="button",this.decrementBtn.className=`${t}__button ${t}__button--decrement`,this.decrementBtn.setAttribute("aria-label","감소"),this.decrementBtn.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/></svg>',this.valueWrapEl=document.createElement("div"),this.valueWrapEl.className=`${t}__value`,this.valueTextEl=document.createElement("span"),this.valueTextEl.className=`${t}__value-text`,this.valueWrapEl.appendChild(this.valueTextEl),this.incrementBtn=document.createElement("button"),this.incrementBtn.type="button",this.incrementBtn.className=`${t}__button ${t}__button--increment`,this.incrementBtn.setAttribute("aria-label","증가"),this.incrementBtn.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',this.descriptionEl=document.createElement("div"),this.descriptionEl.id="stepper-description",this.descriptionEl.className="sr-only",this.decrementBtn.addEventListener("click",()=>this.change(this.value-this.getStep())),this.incrementBtn.addEventListener("click",()=>this.change(this.value+this.getStep())),this.append(this.decrementBtn,this.valueWrapEl,this.incrementBtn,this.descriptionEl)}isEditable(){return!this.hasAttribute("disabled")&&!this.hasAttribute("readonly")&&"false"!==this.getAttribute("editable")}syncFromAttributes(){const i=this.getAttribute("size")||"m",n=this.hasAttribute("disabled"),s=this.hasAttribute("readonly"),l=this.getMin(),u=this.getMax(),a=this.getStep(),r=this.clamp(this.snapToStep(this.value));if(this.getAttribute("value")!==String(r)&&this.setAttribute("value",String(r)),this.className=[t,`${t}--${e.includes(i)?i:"m"}`,this.hasAttribute("full-width")&&`${t}--full-width`,this.isEditable()&&`${t}--editable`,n&&`${t}--disabled`,s&&`${t}--readonly`].filter(Boolean).join(" "),this.syncValueField(r),this.descriptionEl){const t=this.isEditable()?"값을 직접 입력할 수 있습니다. ":"";this.descriptionEl.textContent=`${t}숫자 조정기. +/- 버튼으로 값을 조정할 수 있습니다. 현재 값: ${r}, 최소값: ${l}, 최대값: ${u}, 단계: ${a}`}this.decrementBtn?.setAttribute("aria-describedby","stepper-description"),this.incrementBtn?.setAttribute("aria-describedby","stepper-description"),this.updateButtons()}applyValueInputAppearance(){this.valueInputEl&&(Object.assign(this.valueInputEl.style,i),this.valueInputEl.style.setProperty("-webkit-appearance","none"),this.valueInputEl.style.setProperty("-moz-appearance","textfield"))}syncValueField(e){if(this.valueWrapEl){if(this.isEditable())return this.valueInputEl||(this.valueInputEl=document.createElement("input"),this.valueInputEl.className=`${t}__value-input`,this.valueInputEl.type="text",this.valueInputEl.setAttribute("inputmode",this.allowsDecimal()?"decimal":"numeric"),this.valueInputEl.setAttribute("pattern",this.buildInputPattern()),this.valueInputEl.setAttribute("aria-label","값 입력"),this.valueInputEl.autocomplete="off",this.valueInputEl.spellcheck=!1,this.valueInputEl.addEventListener("keydown",this.boundInputKeyDown),this.valueInputEl.addEventListener("input",this.boundInputInput),this.valueInputEl.addEventListener("paste",this.boundInputPaste),this.valueInputEl.addEventListener("blur",this.boundInputBlur),this.applyValueInputAppearance()),this.applyValueInputAppearance(),this.valueInputEl.setAttribute("inputmode",this.allowsDecimal()?"decimal":"numeric"),this.valueInputEl.setAttribute("pattern",this.buildInputPattern()),this.valueInputEl.setAttribute("aria-valuemin",String(this.getMin())),this.valueInputEl.setAttribute("aria-valuemax",String(this.getMax())),this.valueInputEl.disabled=!1,document.activeElement!==this.valueInputEl&&(this.valueInputEl.value=String(e)),this.valueTextEl?.remove(),void(this.valueWrapEl.contains(this.valueInputEl)||this.valueWrapEl.appendChild(this.valueInputEl));this.valueInputEl&&(this.valueInputEl.removeEventListener("keydown",this.boundInputKeyDown),this.valueInputEl.removeEventListener("input",this.boundInputInput),this.valueInputEl.removeEventListener("paste",this.boundInputPaste),this.valueInputEl.removeEventListener("blur",this.boundInputBlur),this.valueInputEl.remove(),this.valueInputEl=null),this.valueTextEl||(this.valueTextEl=document.createElement("span"),this.valueTextEl.className=`${t}__value-text`),this.valueTextEl.textContent=String(e),this.valueWrapEl.contains(this.valueTextEl)||this.valueWrapEl.appendChild(this.valueTextEl)}}allowsNegative(){return this.getMin()<0}allowsDecimal(){const t=this.getStep();return!Number.isInteger(t)}buildInputPattern(){return`^${this.allowsNegative()?"-?":""}\\d*${this.allowsDecimal()?"(\\.\\d*)?":""}$`}sanitizeInputString(t){let e="",i=!1,n=!1;for(let s=0;s<t.length;s++){const l=t[s];"-"!==l||!this.allowsNegative()||0!==e.length||i?"."!==l||!this.allowsDecimal()||n?/\d/.test(l)&&(e+=l):(e+=l,n=!0):(e+=l,i=!0)}return e}parseInputValue(t){const e=t.trim();if(""===e||"-"===e||"."===e)return null;const i=Number(e);return Number.isFinite(i)?i:null}enforceInputBounds(t,e={}){const i=this.sanitizeInputString(t),n=this.parseInputValue(i);if(null===n)return i;const s=this.getMin(),l=this.getMax();return n>l?String(l):e.clampMin&&n<s?String(s):i}handleInputKeyDown(t){if(!["Backspace","Delete","Tab","Escape","ArrowLeft","ArrowRight","Home","End"].includes(t.key)){if("Enter"===t.key)return t.preventDefault(),this.commitInputValue(),void this.valueInputEl?.blur();if("-"===t.key&&this.allowsNegative()){const t=this.valueInputEl;if(t&&0===t.selectionStart&&0===t.selectionEnd&&!t.value.includes("-"))return}"."===t.key&&this.allowsDecimal()&&!this.valueInputEl?.value.includes(".")||/^\d$/.test(t.key)||t.preventDefault()}}handleInputInput(){if(!this.valueInputEl)return;const t=this.enforceInputBounds(this.valueInputEl.value);this.valueInputEl.value!==t&&(this.valueInputEl.value=t)}handleInputPaste(t){if(t.preventDefault(),!this.valueInputEl)return;const e=t.clipboardData?.getData("text")??"",i=this.enforceInputBounds(this.sanitizeInputString(this.valueInputEl.value+e));this.valueInputEl.value=i,this.handleInputInput()}commitInputValue(){if(!this.valueInputEl||!this.isEditable())return;const t=this.valueInputEl.value.trim();if(""===t||"-"===t||"."===t)return void(this.valueInputEl.value=String(this.value));const e=this.parseInputValue(t);if(null===e)return void(this.valueInputEl.value=String(this.value));const i=this.snapToStep(e);this.valueInputEl.value=String(i),this.change(i)}getMin(){return Number(this.getAttribute("min")??0)}getMax(){return Number(this.getAttribute("max")??100)}getStep(){return Number(this.getAttribute("step")??1)}snapToStep(t){const e=this.getStep(),i=Math.round(t/e)*e;return this.clamp(i)}clamp(t){return Math.max(this.getMin(),Math.min(this.getMax(),t))}updateButtons(){const e=this.getMin(),i=this.getMax(),n=this.hasAttribute("disabled"),s=this.hasAttribute("readonly"),l=this.value,u=l<=e,a=l>=i;if(this.decrementBtn){const e=n||s||u;this.decrementBtn.disabled=e,this.decrementBtn.classList.toggle(`${t}__button--disabled`,e)}if(this.incrementBtn){const e=n||s||a;this.incrementBtn.disabled=e,this.incrementBtn.classList.toggle(`${t}__button--disabled`,e)}}change(t){if(this.hasAttribute("disabled")||this.hasAttribute("readonly"))return;const e=this.getMin(),i=this.getMax(),n=this.clamp(this.snapToStep(t)),s=this.value;if(n!==s){if(this.setAttribute("value",String(n)),this.valueTextEl&&(this.valueTextEl.textContent=String(n)),this.valueInputEl&&(this.valueInputEl.value=String(n)),this.updateButtons(),this.descriptionEl){const t=this.getStep(),s=this.isEditable()?"값을 직접 입력할 수 있습니다. ":"";this.descriptionEl.textContent=`${s}숫자 조정기. +/- 버튼으로 값을 조정할 수 있습니다. 현재 값: ${n}, 최소값: ${e}, 최대값: ${i}, 단계: ${t}`}this.dispatchEvent(new CustomEvent("db-change",{detail:{value:n,minReached:n<=e,maxReached:n>=i},bubbles:!0,composed:!0})),n<=e&&s>e&&this.dispatchEvent(new CustomEvent("db-min-reached",{bubbles:!0,composed:!0,detail:{value:n}})),n>=i&&s<i&&this.dispatchEvent(new CustomEvent("db-max-reached",{bubbles:!0,composed:!0,detail:{value:n}}))}else this.valueInputEl&&(this.valueInputEl.value=String(n))}}customElements.get("db-stepper")||customElements.define("db-stepper",DbStepper);export{DbStepper};
1
+ /**
2
+ * Stepper Web Component
3
+ */
4
+ const CLASS = 'designbase-wc-stepper';
5
+ const SIZES = ['s', 'm', 'l'];
6
+ const MINUS_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/></svg>';
7
+ const PLUS_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>';
8
+ const VALUE_INPUT_APPEARANCE = {
9
+ display: 'block',
10
+ width: '100%',
11
+ minWidth: '0',
12
+ height: 'auto',
13
+ padding: '0',
14
+ margin: '0',
15
+ border: '0',
16
+ background: 'transparent',
17
+ color: 'inherit',
18
+ fontFamily: 'inherit',
19
+ fontWeight: '600',
20
+ lineHeight: '1',
21
+ textAlign: 'center',
22
+ outline: 'none',
23
+ boxShadow: 'none',
24
+ boxSizing: 'border-box',
25
+ appearance: 'none',
26
+ };
27
+ class DbStepper extends HTMLElement {
28
+ constructor() {
29
+ super(...arguments);
30
+ this.decrementBtn = null;
31
+ this.incrementBtn = null;
32
+ this.valueWrapEl = null;
33
+ this.valueTextEl = null;
34
+ this.valueInputEl = null;
35
+ this.descriptionEl = null;
36
+ this.boundInputKeyDown = (e) => this.handleInputKeyDown(e);
37
+ this.boundInputInput = () => this.handleInputInput();
38
+ this.boundInputPaste = (e) => this.handleInputPaste(e);
39
+ this.boundInputBlur = () => this.commitInputValue();
40
+ }
41
+ static get observedAttributes() {
42
+ return ['value', 'min', 'max', 'step', 'size', 'editable', 'full-width', 'disabled', 'readonly'];
43
+ }
44
+ connectedCallback() {
45
+ if (!this.querySelector(`.${CLASS}__button`)) {
46
+ this.build();
47
+ }
48
+ this.syncFromAttributes();
49
+ }
50
+ attributeChangedCallback(name) {
51
+ if (!this.decrementBtn)
52
+ return;
53
+ if (name === 'editable' || name === 'disabled' || name === 'readonly') {
54
+ this.syncFromAttributes();
55
+ return;
56
+ }
57
+ this.syncFromAttributes();
58
+ }
59
+ get value() {
60
+ return Number(this.getAttribute('value') || 0);
61
+ }
62
+ set value(value) {
63
+ this.setAttribute('value', String(value));
64
+ }
65
+ build() {
66
+ this.decrementBtn = document.createElement('button');
67
+ this.decrementBtn.type = 'button';
68
+ this.decrementBtn.className = `${CLASS}__button ${CLASS}__button--decrement`;
69
+ this.decrementBtn.setAttribute('aria-label', '감소');
70
+ this.decrementBtn.innerHTML = MINUS_SVG;
71
+ this.valueWrapEl = document.createElement('div');
72
+ this.valueWrapEl.className = `${CLASS}__value`;
73
+ this.valueTextEl = document.createElement('span');
74
+ this.valueTextEl.className = `${CLASS}__value-text`;
75
+ this.valueWrapEl.appendChild(this.valueTextEl);
76
+ this.incrementBtn = document.createElement('button');
77
+ this.incrementBtn.type = 'button';
78
+ this.incrementBtn.className = `${CLASS}__button ${CLASS}__button--increment`;
79
+ this.incrementBtn.setAttribute('aria-label', '증가');
80
+ this.incrementBtn.innerHTML = PLUS_SVG;
81
+ this.descriptionEl = document.createElement('div');
82
+ this.descriptionEl.id = 'stepper-description';
83
+ this.descriptionEl.className = 'sr-only';
84
+ this.decrementBtn.addEventListener('click', () => this.change(this.value - this.getStep()));
85
+ this.incrementBtn.addEventListener('click', () => this.change(this.value + this.getStep()));
86
+ this.append(this.decrementBtn, this.valueWrapEl, this.incrementBtn, this.descriptionEl);
87
+ }
88
+ isEditable() {
89
+ if (this.hasAttribute('disabled') || this.hasAttribute('readonly')) {
90
+ return false;
91
+ }
92
+ return this.getAttribute('editable') !== 'false';
93
+ }
94
+ syncFromAttributes() {
95
+ const size = this.getAttribute('size') || 'm';
96
+ const disabled = this.hasAttribute('disabled');
97
+ const readOnly = this.hasAttribute('readonly');
98
+ const min = this.getMin();
99
+ const max = this.getMax();
100
+ const step = this.getStep();
101
+ const value = this.clamp(this.snapToStep(this.value));
102
+ if (this.getAttribute('value') !== String(value)) {
103
+ this.setAttribute('value', String(value));
104
+ }
105
+ this.className = [
106
+ CLASS,
107
+ `${CLASS}--${SIZES.includes(size) ? size : 'm'}`,
108
+ this.hasAttribute('full-width') && `${CLASS}--full-width`,
109
+ this.isEditable() && `${CLASS}--editable`,
110
+ disabled && `${CLASS}--disabled`,
111
+ readOnly && `${CLASS}--readonly`,
112
+ ]
113
+ .filter(Boolean)
114
+ .join(' ');
115
+ this.syncValueField(value);
116
+ if (this.descriptionEl) {
117
+ const editHint = this.isEditable() ? '값을 직접 입력할 수 있습니다. ' : '';
118
+ this.descriptionEl.textContent =
119
+ `${editHint}숫자 조정기. +/- 버튼으로 값을 조정할 수 있습니다. 현재 값: ${value}, 최소값: ${min}, 최대값: ${max}, 단계: ${step}`;
120
+ }
121
+ this.decrementBtn?.setAttribute('aria-describedby', 'stepper-description');
122
+ this.incrementBtn?.setAttribute('aria-describedby', 'stepper-description');
123
+ this.updateButtons();
124
+ }
125
+ applyValueInputAppearance() {
126
+ if (!this.valueInputEl)
127
+ return;
128
+ Object.assign(this.valueInputEl.style, VALUE_INPUT_APPEARANCE);
129
+ this.valueInputEl.style.setProperty('-webkit-appearance', 'none');
130
+ this.valueInputEl.style.setProperty('-moz-appearance', 'textfield');
131
+ }
132
+ syncValueField(value) {
133
+ if (!this.valueWrapEl)
134
+ return;
135
+ if (this.isEditable()) {
136
+ if (!this.valueInputEl) {
137
+ this.valueInputEl = document.createElement('input');
138
+ this.valueInputEl.className = `${CLASS}__value-input`;
139
+ this.valueInputEl.type = 'text';
140
+ this.valueInputEl.setAttribute('inputmode', this.allowsDecimal() ? 'decimal' : 'numeric');
141
+ this.valueInputEl.setAttribute('pattern', this.buildInputPattern());
142
+ this.valueInputEl.setAttribute('aria-label', '값 입력');
143
+ this.valueInputEl.autocomplete = 'off';
144
+ this.valueInputEl.spellcheck = false;
145
+ this.valueInputEl.addEventListener('keydown', this.boundInputKeyDown);
146
+ this.valueInputEl.addEventListener('input', this.boundInputInput);
147
+ this.valueInputEl.addEventListener('paste', this.boundInputPaste);
148
+ this.valueInputEl.addEventListener('blur', this.boundInputBlur);
149
+ this.applyValueInputAppearance();
150
+ }
151
+ this.applyValueInputAppearance();
152
+ this.valueInputEl.setAttribute('inputmode', this.allowsDecimal() ? 'decimal' : 'numeric');
153
+ this.valueInputEl.setAttribute('pattern', this.buildInputPattern());
154
+ this.valueInputEl.setAttribute('aria-valuemin', String(this.getMin()));
155
+ this.valueInputEl.setAttribute('aria-valuemax', String(this.getMax()));
156
+ this.valueInputEl.disabled = false;
157
+ if (document.activeElement !== this.valueInputEl) {
158
+ this.valueInputEl.value = String(value);
159
+ }
160
+ this.valueTextEl?.remove();
161
+ if (!this.valueWrapEl.contains(this.valueInputEl)) {
162
+ this.valueWrapEl.appendChild(this.valueInputEl);
163
+ }
164
+ return;
165
+ }
166
+ if (this.valueInputEl) {
167
+ this.valueInputEl.removeEventListener('keydown', this.boundInputKeyDown);
168
+ this.valueInputEl.removeEventListener('input', this.boundInputInput);
169
+ this.valueInputEl.removeEventListener('paste', this.boundInputPaste);
170
+ this.valueInputEl.removeEventListener('blur', this.boundInputBlur);
171
+ this.valueInputEl.remove();
172
+ this.valueInputEl = null;
173
+ }
174
+ if (!this.valueTextEl) {
175
+ this.valueTextEl = document.createElement('span');
176
+ this.valueTextEl.className = `${CLASS}__value-text`;
177
+ }
178
+ this.valueTextEl.textContent = String(value);
179
+ if (!this.valueWrapEl.contains(this.valueTextEl)) {
180
+ this.valueWrapEl.appendChild(this.valueTextEl);
181
+ }
182
+ }
183
+ allowsNegative() {
184
+ return this.getMin() < 0;
185
+ }
186
+ allowsDecimal() {
187
+ const step = this.getStep();
188
+ return !Number.isInteger(step);
189
+ }
190
+ buildInputPattern() {
191
+ const sign = this.allowsNegative() ? '-?' : '';
192
+ const fraction = this.allowsDecimal() ? '(\\.\\d*)?' : '';
193
+ return `^${sign}\\d*${fraction}$`;
194
+ }
195
+ sanitizeInputString(raw) {
196
+ let result = '';
197
+ let hasMinus = false;
198
+ let hasDot = false;
199
+ for (let i = 0; i < raw.length; i++) {
200
+ const ch = raw[i];
201
+ if (ch === '-' && this.allowsNegative() && result.length === 0 && !hasMinus) {
202
+ result += ch;
203
+ hasMinus = true;
204
+ continue;
205
+ }
206
+ if (ch === '.' && this.allowsDecimal() && !hasDot) {
207
+ result += ch;
208
+ hasDot = true;
209
+ continue;
210
+ }
211
+ if (/\d/.test(ch)) {
212
+ result += ch;
213
+ }
214
+ }
215
+ return result;
216
+ }
217
+ parseInputValue(raw) {
218
+ const trimmed = raw.trim();
219
+ if (trimmed === '' || trimmed === '-' || trimmed === '.')
220
+ return null;
221
+ const parsed = Number(trimmed);
222
+ return Number.isFinite(parsed) ? parsed : null;
223
+ }
224
+ enforceInputBounds(raw, options = {}) {
225
+ const sanitized = this.sanitizeInputString(raw);
226
+ const parsed = this.parseInputValue(sanitized);
227
+ if (parsed === null)
228
+ return sanitized;
229
+ const min = this.getMin();
230
+ const max = this.getMax();
231
+ if (parsed > max)
232
+ return String(max);
233
+ if (options.clampMin && parsed < min)
234
+ return String(min);
235
+ return sanitized;
236
+ }
237
+ handleInputKeyDown(e) {
238
+ const controlKeys = [
239
+ 'Backspace',
240
+ 'Delete',
241
+ 'Tab',
242
+ 'Escape',
243
+ 'ArrowLeft',
244
+ 'ArrowRight',
245
+ 'Home',
246
+ 'End',
247
+ ];
248
+ if (controlKeys.includes(e.key))
249
+ return;
250
+ if (e.key === 'Enter') {
251
+ e.preventDefault();
252
+ this.commitInputValue();
253
+ this.valueInputEl?.blur();
254
+ return;
255
+ }
256
+ if (e.key === '-' && this.allowsNegative()) {
257
+ const input = this.valueInputEl;
258
+ if (input &&
259
+ input.selectionStart === 0 &&
260
+ input.selectionEnd === 0 &&
261
+ !input.value.includes('-')) {
262
+ return;
263
+ }
264
+ }
265
+ if (e.key === '.' && this.allowsDecimal() && !this.valueInputEl?.value.includes('.')) {
266
+ return;
267
+ }
268
+ if (/^\d$/.test(e.key))
269
+ return;
270
+ e.preventDefault();
271
+ }
272
+ handleInputInput() {
273
+ if (!this.valueInputEl)
274
+ return;
275
+ const next = this.enforceInputBounds(this.valueInputEl.value);
276
+ if (this.valueInputEl.value !== next) {
277
+ this.valueInputEl.value = next;
278
+ }
279
+ }
280
+ handleInputPaste(e) {
281
+ e.preventDefault();
282
+ if (!this.valueInputEl)
283
+ return;
284
+ const pasted = e.clipboardData?.getData('text') ?? '';
285
+ const merged = this.enforceInputBounds(this.sanitizeInputString(this.valueInputEl.value + pasted));
286
+ this.valueInputEl.value = merged;
287
+ this.handleInputInput();
288
+ }
289
+ commitInputValue() {
290
+ if (!this.valueInputEl || !this.isEditable())
291
+ return;
292
+ const raw = this.valueInputEl.value.trim();
293
+ if (raw === '' || raw === '-' || raw === '.') {
294
+ this.valueInputEl.value = String(this.value);
295
+ return;
296
+ }
297
+ const parsed = this.parseInputValue(raw);
298
+ if (parsed === null) {
299
+ this.valueInputEl.value = String(this.value);
300
+ return;
301
+ }
302
+ const normalized = this.snapToStep(parsed);
303
+ this.valueInputEl.value = String(normalized);
304
+ this.change(normalized);
305
+ }
306
+ getMin() {
307
+ return Number(this.getAttribute('min') ?? 0);
308
+ }
309
+ getMax() {
310
+ return Number(this.getAttribute('max') ?? 100);
311
+ }
312
+ getStep() {
313
+ return Number(this.getAttribute('step') ?? 1);
314
+ }
315
+ snapToStep(value) {
316
+ const step = this.getStep();
317
+ const stepped = Math.round(value / step) * step;
318
+ return this.clamp(stepped);
319
+ }
320
+ clamp(next) {
321
+ return Math.max(this.getMin(), Math.min(this.getMax(), next));
322
+ }
323
+ updateButtons() {
324
+ const min = this.getMin();
325
+ const max = this.getMax();
326
+ const disabled = this.hasAttribute('disabled');
327
+ const readOnly = this.hasAttribute('readonly');
328
+ const value = this.value;
329
+ const isMin = value <= min;
330
+ const isMax = value >= max;
331
+ if (this.decrementBtn) {
332
+ const decDisabled = disabled || readOnly || isMin;
333
+ this.decrementBtn.disabled = decDisabled;
334
+ this.decrementBtn.classList.toggle(`${CLASS}__button--disabled`, decDisabled);
335
+ }
336
+ if (this.incrementBtn) {
337
+ const incDisabled = disabled || readOnly || isMax;
338
+ this.incrementBtn.disabled = incDisabled;
339
+ this.incrementBtn.classList.toggle(`${CLASS}__button--disabled`, incDisabled);
340
+ }
341
+ }
342
+ change(next) {
343
+ if (this.hasAttribute('disabled') || this.hasAttribute('readonly'))
344
+ return;
345
+ const min = this.getMin();
346
+ const max = this.getMax();
347
+ const normalized = this.clamp(this.snapToStep(next));
348
+ const prev = this.value;
349
+ if (normalized === prev) {
350
+ if (this.valueInputEl) {
351
+ this.valueInputEl.value = String(normalized);
352
+ }
353
+ return;
354
+ }
355
+ this.setAttribute('value', String(normalized));
356
+ if (this.valueTextEl) {
357
+ this.valueTextEl.textContent = String(normalized);
358
+ }
359
+ if (this.valueInputEl) {
360
+ this.valueInputEl.value = String(normalized);
361
+ }
362
+ this.updateButtons();
363
+ if (this.descriptionEl) {
364
+ const step = this.getStep();
365
+ const editHint = this.isEditable() ? '값을 직접 입력할 수 있습니다. ' : '';
366
+ this.descriptionEl.textContent =
367
+ `${editHint}숫자 조정기. +/- 버튼으로 값을 조정할 수 있습니다. 현재 값: ${normalized}, 최소값: ${min}, 최대값: ${max}, 단계: ${step}`;
368
+ }
369
+ this.dispatchEvent(new CustomEvent('db-change', {
370
+ detail: {
371
+ value: normalized,
372
+ minReached: normalized <= min,
373
+ maxReached: normalized >= max,
374
+ },
375
+ bubbles: true,
376
+ composed: true,
377
+ }));
378
+ if (normalized <= min && prev > min) {
379
+ this.dispatchEvent(new CustomEvent('db-min-reached', {
380
+ bubbles: true,
381
+ composed: true,
382
+ detail: { value: normalized },
383
+ }));
384
+ }
385
+ if (normalized >= max && prev < max) {
386
+ this.dispatchEvent(new CustomEvent('db-max-reached', {
387
+ bubbles: true,
388
+ composed: true,
389
+ detail: { value: normalized },
390
+ }));
391
+ }
392
+ }
393
+ }
394
+ if (!customElements.get('db-stepper')) {
395
+ customElements.define('db-stepper', DbStepper);
396
+ }
397
+
398
+ export { DbStepper };