snice 1.14.3 → 2.1.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.
Files changed (185) hide show
  1. package/bin/templates/base/tsconfig.json +5 -4
  2. package/components/accordion/demo.html +403 -0
  3. package/components/accordion/snice-accordion-item.css +85 -0
  4. package/components/accordion/snice-accordion-item.ts +226 -0
  5. package/components/accordion/snice-accordion.css +31 -0
  6. package/components/accordion/snice-accordion.ts +182 -0
  7. package/components/accordion/snice-accordion.types.ts +32 -0
  8. package/components/alert/demo.html +445 -0
  9. package/components/alert/snice-alert.css +195 -0
  10. package/components/alert/snice-alert.ts +141 -0
  11. package/components/alert/snice-alert.types.ts +12 -0
  12. package/components/avatar/demo.html +598 -0
  13. package/components/avatar/snice-avatar.css +131 -0
  14. package/components/avatar/snice-avatar.ts +136 -0
  15. package/components/avatar/snice-avatar.types.ts +13 -0
  16. package/components/badge/demo.html +523 -0
  17. package/components/badge/snice-badge.css +161 -0
  18. package/components/badge/snice-badge.ts +117 -0
  19. package/components/badge/snice-badge.types.ts +16 -0
  20. package/components/breadcrumbs/demo.html +404 -0
  21. package/components/breadcrumbs/snice-breadcrumbs.css +133 -0
  22. package/components/breadcrumbs/snice-breadcrumbs.ts +191 -0
  23. package/components/breadcrumbs/snice-breadcrumbs.types.ts +26 -0
  24. package/components/breadcrumbs/snice-crumb.ts +26 -0
  25. package/components/button/demo.html +42 -0
  26. package/components/button/snice-button.css +230 -0
  27. package/components/button/snice-button.ts +169 -0
  28. package/components/button/snice-button.types.ts +25 -0
  29. package/components/card/demo.html +525 -0
  30. package/components/card/snice-card.css +140 -0
  31. package/components/card/snice-card.ts +102 -0
  32. package/components/card/snice-card.types.ts +10 -0
  33. package/components/checkbox/demo.html +253 -0
  34. package/components/checkbox/snice-checkbox.css +164 -0
  35. package/components/checkbox/snice-checkbox.ts +223 -0
  36. package/components/checkbox/snice-checkbox.types.ts +22 -0
  37. package/components/chip/demo.html +383 -0
  38. package/components/chip/snice-chip.css +195 -0
  39. package/components/chip/snice-chip.ts +139 -0
  40. package/components/chip/snice-chip.types.ts +15 -0
  41. package/components/date-picker/README.md +233 -0
  42. package/components/date-picker/demo.html +191 -0
  43. package/components/date-picker/snice-date-picker.css +330 -0
  44. package/components/date-picker/snice-date-picker.ts +777 -0
  45. package/components/date-picker/snice-date-picker.types.ts +83 -0
  46. package/components/divider/demo.html +233 -0
  47. package/components/divider/snice-divider.css +155 -0
  48. package/components/divider/snice-divider.ts +69 -0
  49. package/components/divider/snice-divider.types.ts +15 -0
  50. package/components/drawer/demo.html +328 -0
  51. package/components/drawer/snice-drawer.css +476 -0
  52. package/components/drawer/snice-drawer.ts +287 -0
  53. package/components/drawer/snice-drawer.types.ts +17 -0
  54. package/components/global.d.ts +14 -0
  55. package/components/input/demo.html +303 -0
  56. package/components/input/snice-input.css +257 -0
  57. package/components/input/snice-input.ts +442 -0
  58. package/components/input/snice-input.types.ts +59 -0
  59. package/components/input/test.html +77 -0
  60. package/components/layout/README.md +260 -0
  61. package/components/layout/demo.html +538 -0
  62. package/components/layout/snice-layout-blog.css +129 -0
  63. package/components/layout/snice-layout-blog.ts +48 -0
  64. package/components/layout/snice-layout-card.css +104 -0
  65. package/components/layout/snice-layout-card.ts +35 -0
  66. package/components/layout/snice-layout-centered.css +51 -0
  67. package/components/layout/snice-layout-centered.ts +22 -0
  68. package/components/layout/snice-layout-dashboard.css +98 -0
  69. package/components/layout/snice-layout-dashboard.ts +45 -0
  70. package/components/layout/snice-layout-fullscreen.css +72 -0
  71. package/components/layout/snice-layout-fullscreen.ts +34 -0
  72. package/components/layout/snice-layout-landing.css +92 -0
  73. package/components/layout/snice-layout-landing.ts +47 -0
  74. package/components/layout/snice-layout-minimal.css +16 -0
  75. package/components/layout/snice-layout-minimal.ts +19 -0
  76. package/components/layout/snice-layout-sidebar.css +117 -0
  77. package/components/layout/snice-layout-sidebar.ts +48 -0
  78. package/components/layout/snice-layout-split.css +103 -0
  79. package/components/layout/snice-layout-split.ts +29 -0
  80. package/components/layout/snice-layout.css +72 -0
  81. package/components/layout/snice-layout.ts +35 -0
  82. package/components/layout/snice-layout.types.ts +5 -0
  83. package/components/login/demo-auth-controller.ts +185 -0
  84. package/components/login/demo.html +470 -0
  85. package/components/login/snice-login.css +204 -0
  86. package/components/login/snice-login.ts +337 -0
  87. package/components/login/snice-login.types.ts +34 -0
  88. package/components/modal/demo.html +291 -0
  89. package/components/modal/snice-modal.css +203 -0
  90. package/components/modal/snice-modal.ts +233 -0
  91. package/components/modal/snice-modal.types.ts +21 -0
  92. package/components/pagination/demo.html +395 -0
  93. package/components/pagination/snice-pagination.ts +333 -0
  94. package/components/pagination/snice-pagination.types.ts +21 -0
  95. package/components/progress/demo.html +510 -0
  96. package/components/progress/snice-progress.css +267 -0
  97. package/components/progress/snice-progress.ts +247 -0
  98. package/components/progress/snice-progress.types.ts +19 -0
  99. package/components/radio/demo.html +287 -0
  100. package/components/radio/snice-radio.css +171 -0
  101. package/components/radio/snice-radio.ts +218 -0
  102. package/components/radio/snice-radio.types.ts +21 -0
  103. package/components/select/demo.html +511 -0
  104. package/components/select/snice-option.ts +52 -0
  105. package/components/select/snice-option.types.ts +14 -0
  106. package/components/select/snice-select.css +392 -0
  107. package/components/select/snice-select.ts +796 -0
  108. package/components/select/snice-select.types.ts +55 -0
  109. package/components/skeleton/demo.html +514 -0
  110. package/components/skeleton/snice-skeleton.css +109 -0
  111. package/components/skeleton/snice-skeleton.ts +126 -0
  112. package/components/skeleton/snice-skeleton.types.ts +11 -0
  113. package/components/switch/demo.html +284 -0
  114. package/components/switch/snice-switch.css +221 -0
  115. package/components/switch/snice-switch.ts +229 -0
  116. package/components/switch/snice-switch.types.ts +23 -0
  117. package/components/symbols.ts +23 -0
  118. package/components/table/demo-table-controller.ts +100 -0
  119. package/components/table/demo.html +480 -0
  120. package/components/table/snice-cell-boolean.ts +112 -0
  121. package/components/table/snice-cell-date.ts +210 -0
  122. package/components/table/snice-cell-duration.ts +91 -0
  123. package/components/table/snice-cell-filesize.ts +90 -0
  124. package/components/table/snice-cell-number.ts +165 -0
  125. package/components/table/snice-cell-progress.ts +83 -0
  126. package/components/table/snice-cell-rating.ts +82 -0
  127. package/components/table/snice-cell-sparkline.ts +253 -0
  128. package/components/table/snice-cell-text.ts +125 -0
  129. package/components/table/snice-cell.css +296 -0
  130. package/components/table/snice-cell.ts +473 -0
  131. package/components/table/snice-column.ts +353 -0
  132. package/components/table/snice-header.css +243 -0
  133. package/components/table/snice-header.ts +261 -0
  134. package/components/table/snice-progress.ts +66 -0
  135. package/components/table/snice-rating.ts +45 -0
  136. package/components/table/snice-row.css +255 -0
  137. package/components/table/snice-row.ts +331 -0
  138. package/components/table/snice-table.css +241 -0
  139. package/components/table/snice-table.ts +737 -0
  140. package/components/table/snice-table.types.ts +158 -0
  141. package/components/tabs/demo.html +487 -0
  142. package/components/tabs/snice-tab-panel.css +264 -0
  143. package/components/tabs/snice-tab-panel.ts +47 -0
  144. package/components/tabs/snice-tab.css +96 -0
  145. package/components/tabs/snice-tab.ts +65 -0
  146. package/components/tabs/snice-tabs.css +189 -0
  147. package/components/tabs/snice-tabs.ts +332 -0
  148. package/components/tabs/snice-tabs.types.ts +28 -0
  149. package/components/theme/theme.css +234 -0
  150. package/components/toast/demo.html +329 -0
  151. package/components/toast/snice-toast-container.ts +256 -0
  152. package/components/toast/snice-toast.css +213 -0
  153. package/components/toast/snice-toast.ts +276 -0
  154. package/components/toast/snice-toast.types.ts +35 -0
  155. package/components/tooltip/demo.html +350 -0
  156. package/components/tooltip/snice-tooltip-portal.css +79 -0
  157. package/components/tooltip/snice-tooltip.css +117 -0
  158. package/components/tooltip/snice-tooltip.ts +612 -0
  159. package/components/tooltip/snice-tooltip.types.ts +32 -0
  160. package/components/transitions.ts +94 -0
  161. package/components/tsconfig.json +18 -0
  162. package/dist/index.cjs +441 -329
  163. package/dist/index.cjs.map +1 -1
  164. package/dist/index.cjs.min.map +1 -1
  165. package/dist/index.esm.js +441 -329
  166. package/dist/index.esm.js.map +1 -1
  167. package/dist/index.esm.min.js +3 -3
  168. package/dist/index.esm.min.js.map +1 -1
  169. package/dist/index.iife.js +441 -329
  170. package/dist/index.iife.js.map +1 -1
  171. package/dist/index.iife.min.js +3 -3
  172. package/dist/index.iife.min.js.map +1 -1
  173. package/dist/symbols.esm.js +1 -1
  174. package/dist/transitions.esm.js +1 -1
  175. package/dist/types/controller.d.ts +1 -1
  176. package/dist/types/element.d.ts +10 -10
  177. package/dist/types/events.d.ts +2 -2
  178. package/dist/types/index.d.ts +1 -1
  179. package/dist/types/observe.d.ts +1 -1
  180. package/dist/types/request-response.d.ts +2 -3
  181. package/dist/types/router.d.ts +1 -1
  182. package/package.json +9 -3
  183. package/dist/index.cjs.min +0 -15
  184. package/dist/symbols.cjs +0 -103
  185. package/dist/transitions.cjs +0 -219
@@ -0,0 +1,257 @@
1
+ :host {
2
+ display: inline-block;
3
+ width: 100%;
4
+ font-family: var(--snice-font-family);
5
+ }
6
+
7
+ .input-wrapper {
8
+ position: relative;
9
+ width: 100%;
10
+ }
11
+
12
+ .input-container {
13
+ position: relative;
14
+ display: flex;
15
+ align-items: center;
16
+ width: 100%;
17
+ transition: all var(--snice-transition-fast) ease;
18
+ }
19
+
20
+ /* Base Input Styles */
21
+ .input {
22
+ flex: 1;
23
+ width: 100%;
24
+ font-family: inherit;
25
+ font-size: var(--snice-font-size-md);
26
+ background: var(--snice-color-background);
27
+ color: var(--snice-color-text);
28
+ border: 1px solid var(--snice-color-border);
29
+ border-radius: var(--snice-border-radius-md);
30
+ transition: all var(--snice-transition-fast) ease;
31
+ outline: none;
32
+ -webkit-appearance: none;
33
+ -moz-appearance: none;
34
+ appearance: none;
35
+ }
36
+
37
+ .input:focus {
38
+ border-color: var(--snice-color-primary);
39
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
40
+ }
41
+
42
+ .input:disabled {
43
+ background: var(--snice-color-background-secondary);
44
+ color: var(--snice-color-text-secondary);
45
+ cursor: not-allowed;
46
+ opacity: 0.6;
47
+ }
48
+
49
+ .input::placeholder {
50
+ color: var(--snice-color-text-secondary);
51
+ opacity: 0.7;
52
+ }
53
+
54
+ /* Size Variants */
55
+ .input--small {
56
+ padding: 0.375rem 0.625rem; /* 6px 10px */
57
+ font-size: var(--snice-font-size-sm);
58
+ }
59
+
60
+ .input--medium {
61
+ padding: 0.5rem 0.75rem; /* 8px 12px */
62
+ font-size: var(--snice-font-size-md);
63
+ }
64
+
65
+ .input--large {
66
+ padding: 0.625rem 0.875rem; /* 10px 14px */
67
+ font-size: var(--snice-font-size-lg);
68
+ }
69
+
70
+ /* Style Variants */
71
+ .input--outlined {
72
+ background: var(--snice-color-background);
73
+ border: 1px solid var(--snice-color-border);
74
+ }
75
+
76
+ .input--filled {
77
+ background: var(--snice-color-background-secondary);
78
+ border: 1px solid transparent;
79
+ border-bottom-color: var(--snice-color-border);
80
+ border-radius: var(--snice-border-radius-md) var(--snice-border-radius-md) 0 0;
81
+ }
82
+
83
+ .input--filled:focus {
84
+ background: var(--snice-color-background-hover);
85
+ border-bottom-color: var(--snice-color-primary);
86
+ }
87
+
88
+ .input--underlined {
89
+ background: transparent;
90
+ border: none;
91
+ border-bottom: 1px solid var(--snice-color-border);
92
+ border-radius: 0;
93
+ padding-left: 0;
94
+ padding-right: 0;
95
+ }
96
+
97
+ .input--underlined:focus {
98
+ border-bottom-color: var(--snice-color-primary);
99
+ box-shadow: none;
100
+ }
101
+
102
+ /* Invalid State */
103
+ .input--invalid {
104
+ border-color: var(--snice-color-danger);
105
+ }
106
+
107
+ .input--invalid:focus {
108
+ border-color: var(--snice-color-danger);
109
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
110
+ }
111
+
112
+ /* With Icons */
113
+ .input--with-prefix-icon {
114
+ padding-left: 2.25rem; /* 36px */
115
+ }
116
+
117
+ .input--with-suffix-icon,
118
+ .input--clearable {
119
+ padding-right: 2.25rem; /* 36px */
120
+ }
121
+
122
+ .input--with-suffix-icon.input--clearable {
123
+ padding-right: 3.75rem; /* 60px */
124
+ }
125
+
126
+ /* Label */
127
+ .label {
128
+ display: block;
129
+ margin-bottom: 0.375rem; /* 6px */
130
+ font-size: var(--snice-font-size-sm);
131
+ font-weight: 500;
132
+ color: var(--snice-color-text);
133
+ }
134
+
135
+ .label--required::after {
136
+ content: ' *';
137
+ color: var(--snice-color-danger);
138
+ }
139
+
140
+ /* Helper and Error Text */
141
+ .helper-text,
142
+ .error-text {
143
+ display: block;
144
+ margin-top: 0.25rem; /* 4px */
145
+ font-size: var(--snice-font-size-xs);
146
+ min-height: 1.125rem; /* 18px */
147
+ }
148
+
149
+ .helper-text {
150
+ color: var(--snice-color-text-secondary);
151
+ }
152
+
153
+ .error-text {
154
+ color: var(--snice-color-danger);
155
+ }
156
+
157
+ /* Icons */
158
+ .icon {
159
+ position: absolute;
160
+ display: flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ width: 1.25rem; /* 20px */
164
+ height: 1.25rem; /* 20px */
165
+ color: var(--snice-color-text-secondary);
166
+ pointer-events: none;
167
+ }
168
+
169
+ .icon--prefix {
170
+ left: 0.625rem; /* 10px */
171
+ }
172
+
173
+ .icon--suffix {
174
+ right: 0.625rem; /* 10px */
175
+ }
176
+
177
+ /* Clear Button */
178
+ .clear-button {
179
+ position: absolute;
180
+ right: 0.5rem; /* 8px */
181
+ display: flex;
182
+ align-items: center;
183
+ justify-content: center;
184
+ width: 1.5rem; /* 24px */
185
+ height: 1.5rem; /* 24px */
186
+ padding: 0.25rem; /* 4px */
187
+ background: transparent;
188
+ border: none;
189
+ border-radius: var(--snice-border-radius-sm);
190
+ color: var(--snice-color-text-secondary);
191
+ cursor: pointer;
192
+ transition: all var(--snice-transition-fast) ease;
193
+ opacity: 0;
194
+ visibility: hidden;
195
+ }
196
+
197
+ .clear-button:hover {
198
+ background: var(--snice-color-background-hover);
199
+ color: var(--snice-color-text);
200
+ }
201
+
202
+ .input-container:hover .clear-button--visible,
203
+ .input:focus ~ .clear-button--visible {
204
+ opacity: 1;
205
+ visibility: visible;
206
+ }
207
+
208
+ .clear-button--with-suffix {
209
+ right: 2rem; /* 32px */
210
+ }
211
+
212
+ /* Password Toggle */
213
+ .password-toggle {
214
+ position: absolute;
215
+ right: 0.5rem; /* 8px */
216
+ display: flex;
217
+ align-items: center;
218
+ justify-content: center;
219
+ width: 1.5rem; /* 24px */
220
+ height: 1.5rem; /* 24px */
221
+ padding: 0.25rem; /* 4px */
222
+ background: transparent;
223
+ border: none;
224
+ border-radius: var(--snice-border-radius-sm);
225
+ color: var(--snice-color-text-secondary);
226
+ cursor: pointer;
227
+ transition: all var(--snice-transition-fast) ease;
228
+ }
229
+
230
+ .password-toggle:hover {
231
+ background: var(--snice-color-background-hover);
232
+ color: var(--snice-color-text);
233
+ }
234
+
235
+ /* Number Input Arrows - Hide native */
236
+ .input[type="number"]::-webkit-inner-spin-button,
237
+ .input[type="number"]::-webkit-outer-spin-button {
238
+ -webkit-appearance: none;
239
+ margin: 0;
240
+ }
241
+
242
+ .input[type="number"] {
243
+ -moz-appearance: textfield;
244
+ }
245
+
246
+ /* Search Input - Hide native clear */
247
+ .input[type="search"]::-webkit-search-decoration,
248
+ .input[type="search"]::-webkit-search-cancel-button,
249
+ .input[type="search"]::-webkit-search-results-button,
250
+ .input[type="search"]::-webkit-search-results-decoration {
251
+ -webkit-appearance: none;
252
+ }
253
+
254
+ /* Focus Within for Container */
255
+ .input-container:focus-within {
256
+ z-index: 1;
257
+ }
@@ -0,0 +1,442 @@
1
+ import { element, property, query, on, watch, dispatch, ready } from 'snice';
2
+ import css from './snice-input.css?inline';
3
+ import type { InputType, InputSize, InputVariant, SniceInputElement } from './snice-input.types';
4
+
5
+ @element('snice-input')
6
+ export class SniceInput extends HTMLElement implements SniceInputElement {
7
+ @property({ reflect: true })
8
+ type: InputType = 'text';
9
+
10
+ @property({ reflect: true })
11
+ size: InputSize = 'medium';
12
+
13
+ @property({ reflect: true })
14
+ variant: InputVariant = 'outlined';
15
+
16
+ @property({ reflect: true })
17
+ value = '';
18
+
19
+ @property({ reflect: true })
20
+ placeholder = '';
21
+
22
+ @property({ reflect: true })
23
+ label = '';
24
+
25
+ @property({ attribute: 'helper-text', reflect: true })
26
+ helperText = '';
27
+
28
+ @property({ attribute: 'error-text', reflect: true })
29
+ errorText = '';
30
+
31
+ @property({ type: Boolean, reflect: true })
32
+ disabled = false;
33
+
34
+ @property({ type: Boolean, reflect: true })
35
+ readonly = false;
36
+
37
+ @property({ type: Boolean, reflect: true })
38
+ required = false;
39
+
40
+ @property({ type: Boolean, reflect: true })
41
+ invalid = false;
42
+
43
+ @property({ type: Boolean, reflect: true })
44
+ clearable = false;
45
+
46
+ @property({ type: Boolean, reflect: true })
47
+ password = false;
48
+
49
+ @property({ reflect: true })
50
+ min = '';
51
+
52
+ @property({ reflect: true })
53
+ max = '';
54
+
55
+ @property({ reflect: true })
56
+ step = '';
57
+
58
+ @property({ reflect: true })
59
+ pattern = '';
60
+
61
+ @property({ type: Number, reflect: true })
62
+ maxlength = -1;
63
+
64
+ @property({ type: Number, reflect: true })
65
+ minlength = -1;
66
+
67
+ @property({ reflect: true })
68
+ autocomplete = '';
69
+
70
+ @property({ reflect: true })
71
+ name = '';
72
+
73
+ @property({ attribute: 'prefix-icon', reflect: true })
74
+ prefixIcon = '';
75
+
76
+ @property({ attribute: 'suffix-icon', reflect: true })
77
+ suffixIcon = '';
78
+
79
+ @query('.input')
80
+ input?: HTMLInputElement;
81
+
82
+ @query('.clear-button')
83
+ clearButton?: HTMLButtonElement;
84
+
85
+ @query('.password-toggle')
86
+ passwordToggle?: HTMLButtonElement;
87
+
88
+ private showPassword = false;
89
+
90
+ html() {
91
+ return /*html*/`
92
+ <div class="input-wrapper">
93
+ ${this.label ? /*html*/`
94
+ <label class="label ${this.required ? 'label--required' : ''}">
95
+ ${this.label}
96
+ </label>
97
+ ` : ''}
98
+
99
+ <div class="input-container">
100
+ ${this.prefixIcon ? /*html*/`
101
+ <span class="icon icon--prefix">${this.prefixIcon}</span>
102
+ ` : ''}
103
+
104
+ <input
105
+ class="input
106
+ input--${this.size}
107
+ input--${this.variant}
108
+ ${this.invalid ? 'input--invalid' : ''}
109
+ ${this.prefixIcon ? 'input--with-prefix-icon' : ''}
110
+ ${this.suffixIcon || (this.type === 'password' && this.password) ? 'input--with-suffix-icon' : ''}
111
+ ${this.clearable ? 'input--clearable' : ''}"
112
+ type="${this.type}"
113
+ value="${this.value}"
114
+ placeholder="${this.placeholder}"
115
+ ${this.disabled ? 'disabled' : ''}
116
+ ${this.readonly ? 'readonly' : ''}
117
+ ${this.required ? 'required' : ''}
118
+ ${this.min ? `min="${this.min}"` : ''}
119
+ ${this.max ? `max="${this.max}"` : ''}
120
+ ${this.step ? `step="${this.step}"` : ''}
121
+ ${this.pattern ? `pattern="${this.pattern}"` : ''}
122
+ ${this.maxlength > 0 ? `maxlength="${this.maxlength}"` : ''}
123
+ ${this.minlength > 0 ? `minlength="${this.minlength}"` : ''}
124
+ ${this.autocomplete ? `autocomplete="${this.autocomplete}"` : ''}
125
+ ${this.name ? `name="${this.name}"` : ''}
126
+ part="input"
127
+ />
128
+
129
+ <button
130
+ class="clear-button ${this.suffixIcon || (this.type === 'password' && this.password) ? 'clear-button--with-suffix' : ''}"
131
+ type="button"
132
+ aria-label="Clear"
133
+ tabindex="-1"
134
+ part="clear"
135
+ style="display: none;"
136
+ >
137
+ <svg viewBox="0 0 24 24" width="16" height="16">
138
+ <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="currentColor"/>
139
+ </svg>
140
+ </button>
141
+
142
+ ${this.type === 'password' && this.password ? /*html*/`
143
+ <button
144
+ class="password-toggle"
145
+ type="button"
146
+ aria-label="Show password"
147
+ tabindex="-1"
148
+ part="password-toggle"
149
+ >
150
+ <svg viewBox="0 0 24 24" width="18" height="18" class="password-icon password-icon--hidden">
151
+ <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z" fill="currentColor"/>
152
+ </svg>
153
+ <svg viewBox="0 0 24 24" width="18" height="18" class="password-icon password-icon--visible" style="display: none;">
154
+ <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" fill="currentColor"/>
155
+ </svg>
156
+ </button>
157
+ ` : this.suffixIcon ? /*html*/`
158
+ <span class="icon icon--suffix">${this.suffixIcon}</span>
159
+ ` : ''}
160
+ </div>
161
+
162
+ ${this.errorText ? /*html*/`
163
+ <span class="error-text" part="error-text">${this.errorText}</span>
164
+ ` : this.helperText ? /*html*/`
165
+ <span class="helper-text" part="helper-text">${this.helperText}</span>
166
+ ` : /*html*/`
167
+ <span class="helper-text" part="helper-text">&nbsp;</span>
168
+ `}
169
+ </div>
170
+ `;
171
+ }
172
+
173
+ css() {
174
+ return css;
175
+ }
176
+
177
+ @ready()
178
+ init() {
179
+ // Set initial clear button visibility
180
+ if (this.clearButton && this.clearable) {
181
+ const shouldShow = this.value && !this.disabled && !this.readonly;
182
+ this.clearButton.style.display = shouldShow ? '' : 'none';
183
+ this.clearButton.classList.toggle('clear-button--visible', !!shouldShow);
184
+ }
185
+
186
+ // Set initial invalid state
187
+ if (this.input && this.invalid) {
188
+ this.input.setAttribute('aria-invalid', 'true');
189
+ this.input.classList.add('input--invalid');
190
+ }
191
+
192
+ // Set initial input properties that may not be properly set via attributes
193
+ if (this.input) {
194
+ // Number input constraints
195
+ if (this.type === 'number') {
196
+ if (this.min) this.input.min = this.min;
197
+ if (this.max) this.input.max = this.max;
198
+ if (this.step) this.input.step = this.step;
199
+ }
200
+
201
+ // Text constraints
202
+ if (this.maxlength > 0) this.input.maxLength = this.maxlength;
203
+ if (this.minlength > 0) this.input.minLength = this.minlength;
204
+
205
+ // Other properties
206
+ if (this.pattern) this.input.pattern = this.pattern;
207
+ if (this.placeholder) this.input.placeholder = this.placeholder;
208
+ if (this.value) this.input.value = this.value;
209
+ this.input.disabled = this.disabled;
210
+ this.input.readOnly = this.readonly;
211
+ this.input.required = this.required;
212
+ }
213
+ }
214
+
215
+ @on('input', '.input')
216
+ handleInput(e: Event) {
217
+ const target = e.target as HTMLInputElement;
218
+ this.value = target.value;
219
+ this.dispatchInputEvent();
220
+ }
221
+
222
+ @on('change', '.input')
223
+ handleChange(e: Event) {
224
+ const target = e.target as HTMLInputElement;
225
+ this.value = target.value;
226
+ this.dispatchChangeEvent();
227
+ }
228
+
229
+ @on('focus', '.input')
230
+ handleFocus() {
231
+ this.dispatchFocusEvent();
232
+ }
233
+
234
+ @on('blur', '.input')
235
+ handleBlur() {
236
+ this.dispatchBlurEvent();
237
+ }
238
+
239
+ @on('click', '.clear-button')
240
+ handleClear() {
241
+ this.clear();
242
+ }
243
+
244
+ @on('click', '.password-toggle')
245
+ handlePasswordToggle() {
246
+ this.showPassword = !this.showPassword;
247
+ if (this.input) {
248
+ this.input.type = this.showPassword ? 'text' : 'password';
249
+ }
250
+ if (this.passwordToggle) {
251
+ const hiddenIcon = this.passwordToggle.querySelector('.password-icon--hidden') as HTMLElement;
252
+ const visibleIcon = this.passwordToggle.querySelector('.password-icon--visible') as HTMLElement;
253
+ if (hiddenIcon && visibleIcon) {
254
+ hiddenIcon.style.display = this.showPassword ? 'none' : '';
255
+ visibleIcon.style.display = this.showPassword ? '' : 'none';
256
+ this.passwordToggle.setAttribute('aria-label', this.showPassword ? 'Hide password' : 'Show password');
257
+ }
258
+ }
259
+ }
260
+
261
+ @watch('value')
262
+ handleValueChange() {
263
+ if (this.input) {
264
+ this.input.value = this.value;
265
+ }
266
+ // Show/hide clear button based on value
267
+ if (this.clearButton && this.clearable) {
268
+ const shouldShow = this.value && !this.disabled && !this.readonly;
269
+ this.clearButton.style.display = shouldShow ? '' : 'none';
270
+ this.clearButton.classList.toggle('clear-button--visible', !!shouldShow);
271
+ }
272
+ }
273
+
274
+ @watch('invalid')
275
+ handleInvalidChange() {
276
+ if (this.input) {
277
+ this.input.setAttribute('aria-invalid', String(this.invalid));
278
+ if (this.invalid) {
279
+ this.input.classList.add('input--invalid');
280
+ } else {
281
+ this.input.classList.remove('input--invalid');
282
+ }
283
+ }
284
+ }
285
+
286
+ @watch('disabled')
287
+ handleDisabledChange() {
288
+ if (this.input) {
289
+ this.input.disabled = this.disabled;
290
+ }
291
+ // Update clear button visibility
292
+ if (this.clearButton && this.clearable) {
293
+ const shouldShow = this.value && !this.disabled && !this.readonly;
294
+ this.clearButton.style.display = shouldShow ? '' : 'none';
295
+ this.clearButton.classList.toggle('clear-button--visible', !!shouldShow);
296
+ }
297
+ }
298
+
299
+ @watch('readonly')
300
+ handleReadonlyChange() {
301
+ if (this.input) {
302
+ this.input.readOnly = this.readonly;
303
+ }
304
+ // Update clear button visibility
305
+ if (this.clearButton && this.clearable) {
306
+ const shouldShow = this.value && !this.disabled && !this.readonly;
307
+ this.clearButton.style.display = shouldShow ? '' : 'none';
308
+ this.clearButton.classList.toggle('clear-button--visible', !!shouldShow);
309
+ }
310
+ }
311
+
312
+ @watch('placeholder')
313
+ handlePlaceholderChange() {
314
+ if (this.input) {
315
+ this.input.placeholder = this.placeholder;
316
+ }
317
+ }
318
+
319
+ @watch('min')
320
+ handleMinChange() {
321
+ if (this.input && this.type === 'number') {
322
+ this.input.min = this.min;
323
+ }
324
+ }
325
+
326
+ @watch('max')
327
+ handleMaxChange() {
328
+ if (this.input && this.type === 'number') {
329
+ this.input.max = this.max;
330
+ }
331
+ }
332
+
333
+ @watch('step')
334
+ handleStepChange() {
335
+ if (this.input && this.type === 'number') {
336
+ this.input.step = this.step;
337
+ }
338
+ }
339
+
340
+ @watch('maxlength')
341
+ handleMaxLengthChange() {
342
+ if (this.input && this.maxlength > 0) {
343
+ this.input.maxLength = this.maxlength;
344
+ }
345
+ }
346
+
347
+ @watch('minlength')
348
+ handleMinLengthChange() {
349
+ if (this.input && this.minlength > 0) {
350
+ this.input.minLength = this.minlength;
351
+ }
352
+ }
353
+
354
+ @watch('pattern')
355
+ handlePatternChange() {
356
+ if (this.input) {
357
+ this.input.pattern = this.pattern;
358
+ }
359
+ }
360
+
361
+ @watch('type')
362
+ handleTypeChange() {
363
+ if (this.input && this.type !== 'password') {
364
+ // Don't change type for password fields (handled by toggle)
365
+ this.input.type = this.type;
366
+ }
367
+ }
368
+
369
+ @watch('label')
370
+ handleLabelChange() {
371
+ const labelEl = this.shadowRoot?.querySelector('.label');
372
+ if (labelEl) {
373
+ labelEl.textContent = this.label;
374
+ // Show/hide the label wrapper
375
+ const wrapper = labelEl.parentElement;
376
+ if (wrapper) {
377
+ wrapper.style.display = this.label ? '' : 'none';
378
+ }
379
+ }
380
+ }
381
+
382
+ @dispatch('@snice/input-input', { bubbles: true, composed: true })
383
+ private dispatchInputEvent() {
384
+ return { value: this.value, input: this };
385
+ }
386
+
387
+ @dispatch('@snice/input-change', { bubbles: true, composed: true })
388
+ private dispatchChangeEvent() {
389
+ return { value: this.value, input: this };
390
+ }
391
+
392
+ @dispatch('@snice/input-focus', { bubbles: true, composed: true })
393
+ private dispatchFocusEvent() {
394
+ return { input: this };
395
+ }
396
+
397
+ @dispatch('@snice/input-blur', { bubbles: true, composed: true })
398
+ private dispatchBlurEvent() {
399
+ return { input: this };
400
+ }
401
+
402
+ @dispatch('@snice/input-clear', { bubbles: true, composed: true })
403
+ private dispatchClearEvent() {
404
+ return { input: this };
405
+ }
406
+
407
+ // Public API
408
+ focus() {
409
+ this.input?.focus();
410
+ }
411
+
412
+ blur() {
413
+ this.input?.blur();
414
+ }
415
+
416
+ select() {
417
+ this.input?.select();
418
+ }
419
+
420
+ clear() {
421
+ this.value = '';
422
+ if (this.input) {
423
+ this.input.value = '';
424
+ }
425
+ this.dispatchClearEvent();
426
+ this.dispatchInputEvent();
427
+ this.dispatchChangeEvent();
428
+ this.focus();
429
+ }
430
+
431
+ checkValidity() {
432
+ return this.input?.checkValidity() ?? true;
433
+ }
434
+
435
+ reportValidity() {
436
+ return this.input?.reportValidity() ?? true;
437
+ }
438
+
439
+ setCustomValidity(message: string) {
440
+ this.input?.setCustomValidity(message);
441
+ }
442
+ }