cynx-lib 1.3.83 → 1.3.85

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 (42) hide show
  1. package/gen.js +77 -77
  2. package/package.json +5 -1
  3. package/ui/components/Drawer.jsx +208 -208
  4. package/ui/components/Layout.jsx +37 -0
  5. package/ui/components/Pagination.jsx +145 -145
  6. package/ui/components/PrivacyBlur.jsx +603 -603
  7. package/ui/components/Sidebar.jsx +300 -134
  8. package/utils/index.cjs +5 -0
  9. package/utils/index.js +22 -20
  10. package/utils/utils/duration.js +139 -139
  11. package/utils/utils/oidc.js +214 -214
  12. package/utils/utils/realtime.js +1634 -1634
  13. package/utils/utils/session.cjs +15 -2
  14. package/utils/utils/session.js +67 -58
  15. package/utils/utils/session_manager.cjs +490 -0
  16. package/utils/utils/session_manager.js +549 -0
  17. package/utils/utils/storage.js +393 -393
  18. package/utils/utils/ui.js +111 -111
  19. package/vue/components/Badge.vue +79 -79
  20. package/vue/components/Button.vue +209 -209
  21. package/vue/components/Checkbox.vue +103 -103
  22. package/vue/components/CircularProgress.vue +383 -383
  23. package/vue/components/Divider.vue +94 -94
  24. package/vue/components/Drawer.vue +243 -243
  25. package/vue/components/InfoBadge.vue +223 -223
  26. package/vue/components/Input.vue +341 -341
  27. package/vue/components/LinearProgress.vue +268 -268
  28. package/vue/components/Modal.vue +226 -226
  29. package/vue/components/Pagination.vue +199 -199
  30. package/vue/components/Select.vue +713 -713
  31. package/vue/components/Sidebar.vue +955 -955
  32. package/vue/components/Skeleton.vue +91 -91
  33. package/vue/components/Spinner.vue +263 -263
  34. package/vue/components/StatusTag.vue +89 -89
  35. package/vue/components/TabBar.vue +176 -176
  36. package/vue/components/Table.vue +388 -388
  37. package/vue/components/TimePicker.vue +399 -399
  38. package/vue/components/Toggle.vue +115 -115
  39. package/vue/components/XTable.vue +119 -119
  40. package/vue/composables/useAutoSkeletonRows.ts +71 -71
  41. package/vue/index.d.ts +67 -67
  42. package/vue/index.js +86 -86
@@ -1,341 +1,341 @@
1
- <script setup lang="ts">
2
- import { ref, computed, useSlots } from "vue";
3
-
4
- const SIZES: Record<string, { height: number; fontSize: number; paddingH: number }> = {
5
- xs: { height: 24, fontSize: 11, paddingH: 6 },
6
- sm: { height: 28, fontSize: 11.5, paddingH: 8 },
7
- md: { height: 32, fontSize: 13, paddingH: 11 },
8
- lg: { height: 36, fontSize: 14, paddingH: 14 },
9
- };
10
-
11
- const props = withDefaults(
12
- defineProps<{
13
- modelValue?: string | number;
14
- label?: string;
15
- required?: boolean;
16
- hint?: string;
17
- error?: string;
18
- size?: "xs" | "sm" | "md" | "lg";
19
- type?: string;
20
- icon?: any;
21
- iconRight?: any;
22
- clearable?: boolean;
23
- suffix?: string;
24
- multiline?: boolean;
25
- disabled?: boolean;
26
- placeholder?: string;
27
- className?: string;
28
- style?: Record<string, any>;
29
- }>(),
30
- {
31
- modelValue: "",
32
- required: false,
33
- size: "md",
34
- type: "text",
35
- clearable: false,
36
- multiline: false,
37
- disabled: false,
38
- placeholder: "",
39
- className: "",
40
- style: () => ({}),
41
- }
42
- );
43
-
44
- const emit = defineEmits<{
45
- (e: "update:modelValue", value: string): void;
46
- (e: "clear"): void;
47
- (e: "focus", event: FocusEvent): void;
48
- (e: "blur", event: FocusEvent): void;
49
- }>();
50
-
51
- const slots = useSlots();
52
- const inputRef = ref<HTMLInputElement | HTMLTextAreaElement | null>(null);
53
- const focused = ref(false);
54
- const hovered = ref(false);
55
- const s = computed(() => SIZES[props.size] || SIZES.md);
56
-
57
- const isPasswd = computed(() => props.type === "passwd");
58
- const resolvedType = computed(() => (isPasswd.value ? "text" : props.type));
59
- const hasValue = computed(
60
- () => props.modelValue !== undefined && props.modelValue !== null && props.modelValue !== ""
61
- );
62
-
63
- const passwdStyle = computed(() =>
64
- isPasswd.value
65
- ? {
66
- WebkitTextSecurity: "disc",
67
- textSecurity: "disc",
68
- fontFamily: hasValue.value ? "monospace" : "var(--f, var(--sans, inherit))",
69
- fontWeight: hasValue.value ? 700 : "normal",
70
- letterSpacing: hasValue.value ? "0.15em" : "normal",
71
- }
72
- : {}
73
- );
74
-
75
- const hasIconLeft = computed(() => Boolean(props.icon) || Boolean(slots.icon));
76
- const hasIconRight = computed(() => Boolean(props.iconRight) || Boolean(slots.iconRight));
77
- const showClear = computed(
78
- () => props.clearable && Boolean(props.modelValue) && !props.disabled && !props.multiline
79
- );
80
- const hasSuffix = computed(() => Boolean(props.suffix) && !props.multiline);
81
- const showRightSlot = computed(() => showClear.value || hasSuffix.value);
82
-
83
- const handleInput = (e: Event) => {
84
- emit("update:modelValue", (e.target as HTMLInputElement).value);
85
- };
86
-
87
- const handleClear = () => {
88
- emit("update:modelValue", "");
89
- emit("clear");
90
- inputRef.value?.focus();
91
- };
92
- </script>
93
-
94
- <template>
95
- <div
96
- class="cynx-input-wrapper"
97
- style="display: flex; flex-direction: column; gap: 5px; width: 100%;"
98
- >
99
- <label
100
- v-if="label"
101
- :style="{
102
- fontSize: size === 'xs' ? '9px' : '11px',
103
- fontWeight: 600,
104
- color: 'var(--t2, #797b8d)',
105
- textTransform: 'uppercase',
106
- letterSpacing: '.08em',
107
- userSelect: 'none',
108
- }"
109
- >
110
- {{ label }}
111
- <span v-if="required" style="color: var(--danger, #f64747); margin-left: 2px;">*</span>
112
- </label>
113
-
114
- <div style="position: relative; display: flex; align-items: center; width: 100%;">
115
- <!-- Left Icon / Text Prefix -->
116
- <span
117
- v-if="hasIconLeft"
118
- style="
119
- position: absolute;
120
- left: 10px;
121
- top: 50%;
122
- transform: translateY(-50%);
123
- color: var(--t3, #9b9daa);
124
- pointer-events: none;
125
- line-height: 0;
126
- display: flex;
127
- align-items: center;
128
- justify-content: center;
129
- z-index: 2;
130
- "
131
- >
132
- <slot name="icon">
133
- <span
134
- v-if="typeof icon === 'string'"
135
- style="font-size: 11px; font-weight: 600; color: var(--t3, #9b9daa); user-select: none;"
136
- >
137
- {{ icon }}
138
- </span>
139
- <component
140
- :is="icon"
141
- v-else
142
- style="width: 15px; height: 15px; color: var(--t3, #9b9daa);"
143
- />
144
- </slot>
145
- </span>
146
-
147
- <!-- Right Icon Slot -->
148
- <span
149
- v-if="hasIconRight"
150
- style="
151
- position: absolute;
152
- right: 10px;
153
- top: 50%;
154
- transform: translateY(-50%);
155
- color: var(--t3, #9b9daa);
156
- pointer-events: none;
157
- display: flex;
158
- align-items: center;
159
- justify-content: center;
160
- z-index: 2;
161
- "
162
- >
163
- <slot name="iconRight">
164
- <component :is="iconRight" style="width: 15px; height: 15px; color: var(--t3, #9b9daa);" />
165
- </slot>
166
- </span>
167
-
168
- <!-- Textarea Multiline -->
169
- <textarea
170
- v-if="multiline"
171
- ref="inputRef"
172
- :value="modelValue"
173
- :placeholder="placeholder"
174
- :disabled="disabled"
175
- :class="['cynx-input-control', className]"
176
- :style="{
177
- display: 'block',
178
- width: '100%',
179
- minHeight: '70px',
180
- resize: 'vertical',
181
- whiteSpace: 'pre-wrap',
182
- overflowWrap: 'break-word',
183
- background: disabled ? 'var(--grey-1, #f5f5f8)' : 'var(--input-bg, #fff)',
184
- border: `1px solid ${error ? 'var(--danger, #f64747)' : focused ? 'var(--accent, #12675D)' : hovered ? 'var(--accent, #12675D)' : 'var(--bd, #d7d8e0)'}`,
185
- borderRadius: 'var(--rads, 8px)',
186
- color: 'var(--t1, #1a1a2e)',
187
- fontFamily: 'var(--f, var(--sans, inherit))',
188
- fontSize: `${s.fontSize}px`,
189
- paddingTop: '8px',
190
- paddingBottom: '8px',
191
- paddingLeft: hasIconLeft ? '34px' : '12px',
192
- paddingRight: (hasIconRight || showRightSlot) ? '34px' : '12px',
193
- outline: 'none',
194
- transition: 'border-color .14s, box-shadow .14s',
195
- boxShadow: error
196
- ? 'var(--dd, rgba(246,71,71,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
197
- : focused
198
- ? 'var(--pd, rgba(18,103,93,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
199
- : 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
200
- opacity: disabled ? 0.5 : 1,
201
- cursor: disabled ? 'not-allowed' : undefined,
202
- ...style,
203
- }"
204
- @input="handleInput"
205
- @focus="focused = true; emit('focus', $event)"
206
- @blur="focused = false; hovered = false; emit('blur', $event)"
207
- />
208
-
209
- <!-- Standard Input Control -->
210
- <input
211
- v-else
212
- ref="inputRef"
213
- :type="resolvedType"
214
- :value="modelValue"
215
- :placeholder="placeholder"
216
- :disabled="disabled"
217
- :class="['cynx-input-control', className]"
218
- :style="{
219
- display: 'block',
220
- width: '100%',
221
- background: disabled ? 'var(--grey-1, #f5f5f8)' : 'var(--input-bg, #fff)',
222
- border: `1px solid ${error ? 'var(--danger, #f64747)' : focused ? 'var(--accent, #12675D)' : hovered ? 'var(--accent, #12675D)' : 'var(--bd, #d7d8e0)'}`,
223
- borderRadius: 'var(--rads, 8px)',
224
- color: 'var(--t1, #1a1a2e)',
225
- fontFamily: 'var(--f, var(--sans, inherit))',
226
- fontSize: `${s.fontSize}px`,
227
- height: `${s.height}px`,
228
- padding: `0 ${s.paddingH}px`,
229
- paddingLeft: hasIconLeft ? '34px' : `${s.paddingH}px`,
230
- paddingRight: (hasIconRight || showRightSlot) ? '34px' : `${s.paddingH}px`,
231
- outline: 'none',
232
- transition: 'border-color .14s, box-shadow .14s',
233
- boxShadow: error
234
- ? 'var(--dd, rgba(246,71,71,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
235
- : focused
236
- ? 'var(--pd, rgba(18,103,93,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
237
- : 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
238
- opacity: disabled ? 0.5 : 1,
239
- cursor: disabled ? 'not-allowed' : undefined,
240
- ...passwdStyle,
241
- ...style,
242
- }"
243
- @input="handleInput"
244
- @focus="focused = true; emit('focus', $event)"
245
- @blur="focused = false; hovered = false; emit('blur', $event)"
246
- />
247
-
248
- <!-- Clear Button (1-to-1 matching Input.jsx) -->
249
- <button
250
- v-if="showClear"
251
- type="button"
252
- tabindex="-1"
253
- style="
254
- position: absolute;
255
- right: 8px;
256
- top: 50%;
257
- transform: translateY(-50%);
258
- display: flex;
259
- align-items: center;
260
- justify-content: center;
261
- width: 18px;
262
- height: 18px;
263
- border-radius: 50%;
264
- background: transparent;
265
- border: none;
266
- cursor: pointer;
267
- color: var(--t3, #9b9daa);
268
- padding: 0;
269
- line-height: 0;
270
- z-index: 2;
271
- "
272
- title="Clear"
273
- @click="handleClear"
274
- >
275
- <svg
276
- width="10"
277
- height="10"
278
- viewBox="0 0 10 10"
279
- fill="none"
280
- stroke="currentColor"
281
- stroke-width="1.8"
282
- stroke-linecap="round"
283
- >
284
- <line x1="1" y1="1" x2="9" y2="9" />
285
- <line x1="9" y1="1" x2="1" y2="9" />
286
- </svg>
287
- </button>
288
-
289
- <!-- Suffix Text / Element -->
290
- <span
291
- v-if="hasSuffix && !showClear"
292
- style="
293
- position: absolute;
294
- right: 8px;
295
- top: 50%;
296
- transform: translateY(-50%);
297
- display: flex;
298
- align-items: center;
299
- gap: 4px;
300
- pointer-events: none;
301
- z-index: 2;
302
- "
303
- >
304
- {{ suffix }}
305
- </span>
306
- </div>
307
-
308
- <!-- Hint / Error Messages -->
309
- <span
310
- v-if="hint && !error"
311
- style="font-size: 11px; color: var(--t3, #9b9daa);"
312
- >
313
- {{ hint }}
314
- </span>
315
- <span
316
- v-if="error"
317
- style="font-size: 11px; color: var(--danger, #f64747);"
318
- >
319
- {{ error }}
320
- </span>
321
- </div>
322
- </template>
323
-
324
- <style scoped>
325
- .cynx-input-control::placeholder {
326
- color: var(--t3, #9b9daa);
327
- opacity: 1;
328
- }
329
-
330
- :root.dark .cynx-input-control,
331
- :root.dark2 .cynx-input-control {
332
- --input-bg: #141629;
333
- --t1: #ffffff;
334
- --bd: #334155;
335
- }
336
-
337
- :root.dark .cynx-input-control::placeholder,
338
- :root.dark2 .cynx-input-control::placeholder {
339
- color: #64748b;
340
- }
341
- </style>
1
+ <script setup lang="ts">
2
+ import { ref, computed, useSlots } from "vue";
3
+
4
+ const SIZES: Record<string, { height: number; fontSize: number; paddingH: number }> = {
5
+ xs: { height: 24, fontSize: 11, paddingH: 6 },
6
+ sm: { height: 28, fontSize: 11.5, paddingH: 8 },
7
+ md: { height: 32, fontSize: 13, paddingH: 11 },
8
+ lg: { height: 36, fontSize: 14, paddingH: 14 },
9
+ };
10
+
11
+ const props = withDefaults(
12
+ defineProps<{
13
+ modelValue?: string | number;
14
+ label?: string;
15
+ required?: boolean;
16
+ hint?: string;
17
+ error?: string;
18
+ size?: "xs" | "sm" | "md" | "lg";
19
+ type?: string;
20
+ icon?: any;
21
+ iconRight?: any;
22
+ clearable?: boolean;
23
+ suffix?: string;
24
+ multiline?: boolean;
25
+ disabled?: boolean;
26
+ placeholder?: string;
27
+ className?: string;
28
+ style?: Record<string, any>;
29
+ }>(),
30
+ {
31
+ modelValue: "",
32
+ required: false,
33
+ size: "md",
34
+ type: "text",
35
+ clearable: false,
36
+ multiline: false,
37
+ disabled: false,
38
+ placeholder: "",
39
+ className: "",
40
+ style: () => ({}),
41
+ }
42
+ );
43
+
44
+ const emit = defineEmits<{
45
+ (e: "update:modelValue", value: string): void;
46
+ (e: "clear"): void;
47
+ (e: "focus", event: FocusEvent): void;
48
+ (e: "blur", event: FocusEvent): void;
49
+ }>();
50
+
51
+ const slots = useSlots();
52
+ const inputRef = ref<HTMLInputElement | HTMLTextAreaElement | null>(null);
53
+ const focused = ref(false);
54
+ const hovered = ref(false);
55
+ const s = computed(() => SIZES[props.size] || SIZES.md);
56
+
57
+ const isPasswd = computed(() => props.type === "passwd");
58
+ const resolvedType = computed(() => (isPasswd.value ? "text" : props.type));
59
+ const hasValue = computed(
60
+ () => props.modelValue !== undefined && props.modelValue !== null && props.modelValue !== ""
61
+ );
62
+
63
+ const passwdStyle = computed(() =>
64
+ isPasswd.value
65
+ ? {
66
+ WebkitTextSecurity: "disc",
67
+ textSecurity: "disc",
68
+ fontFamily: hasValue.value ? "monospace" : "var(--f, var(--sans, inherit))",
69
+ fontWeight: hasValue.value ? 700 : "normal",
70
+ letterSpacing: hasValue.value ? "0.15em" : "normal",
71
+ }
72
+ : {}
73
+ );
74
+
75
+ const hasIconLeft = computed(() => Boolean(props.icon) || Boolean(slots.icon));
76
+ const hasIconRight = computed(() => Boolean(props.iconRight) || Boolean(slots.iconRight));
77
+ const showClear = computed(
78
+ () => props.clearable && Boolean(props.modelValue) && !props.disabled && !props.multiline
79
+ );
80
+ const hasSuffix = computed(() => Boolean(props.suffix) && !props.multiline);
81
+ const showRightSlot = computed(() => showClear.value || hasSuffix.value);
82
+
83
+ const handleInput = (e: Event) => {
84
+ emit("update:modelValue", (e.target as HTMLInputElement).value);
85
+ };
86
+
87
+ const handleClear = () => {
88
+ emit("update:modelValue", "");
89
+ emit("clear");
90
+ inputRef.value?.focus();
91
+ };
92
+ </script>
93
+
94
+ <template>
95
+ <div
96
+ class="cynx-input-wrapper"
97
+ style="display: flex; flex-direction: column; gap: 5px; width: 100%;"
98
+ >
99
+ <label
100
+ v-if="label"
101
+ :style="{
102
+ fontSize: size === 'xs' ? '9px' : '11px',
103
+ fontWeight: 600,
104
+ color: 'var(--t2, #797b8d)',
105
+ textTransform: 'uppercase',
106
+ letterSpacing: '.08em',
107
+ userSelect: 'none',
108
+ }"
109
+ >
110
+ {{ label }}
111
+ <span v-if="required" style="color: var(--danger, #f64747); margin-left: 2px;">*</span>
112
+ </label>
113
+
114
+ <div style="position: relative; display: flex; align-items: center; width: 100%;">
115
+ <!-- Left Icon / Text Prefix -->
116
+ <span
117
+ v-if="hasIconLeft"
118
+ style="
119
+ position: absolute;
120
+ left: 10px;
121
+ top: 50%;
122
+ transform: translateY(-50%);
123
+ color: var(--t3, #9b9daa);
124
+ pointer-events: none;
125
+ line-height: 0;
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ z-index: 2;
130
+ "
131
+ >
132
+ <slot name="icon">
133
+ <span
134
+ v-if="typeof icon === 'string'"
135
+ style="font-size: 11px; font-weight: 600; color: var(--t3, #9b9daa); user-select: none;"
136
+ >
137
+ {{ icon }}
138
+ </span>
139
+ <component
140
+ :is="icon"
141
+ v-else
142
+ style="width: 15px; height: 15px; color: var(--t3, #9b9daa);"
143
+ />
144
+ </slot>
145
+ </span>
146
+
147
+ <!-- Right Icon Slot -->
148
+ <span
149
+ v-if="hasIconRight"
150
+ style="
151
+ position: absolute;
152
+ right: 10px;
153
+ top: 50%;
154
+ transform: translateY(-50%);
155
+ color: var(--t3, #9b9daa);
156
+ pointer-events: none;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ z-index: 2;
161
+ "
162
+ >
163
+ <slot name="iconRight">
164
+ <component :is="iconRight" style="width: 15px; height: 15px; color: var(--t3, #9b9daa);" />
165
+ </slot>
166
+ </span>
167
+
168
+ <!-- Textarea Multiline -->
169
+ <textarea
170
+ v-if="multiline"
171
+ ref="inputRef"
172
+ :value="modelValue"
173
+ :placeholder="placeholder"
174
+ :disabled="disabled"
175
+ :class="['cynx-input-control', className]"
176
+ :style="{
177
+ display: 'block',
178
+ width: '100%',
179
+ minHeight: '70px',
180
+ resize: 'vertical',
181
+ whiteSpace: 'pre-wrap',
182
+ overflowWrap: 'break-word',
183
+ background: disabled ? 'var(--grey-1, #f5f5f8)' : 'var(--input-bg, #fff)',
184
+ border: `1px solid ${error ? 'var(--danger, #f64747)' : focused ? 'var(--accent, #12675D)' : hovered ? 'var(--accent, #12675D)' : 'var(--bd, #d7d8e0)'}`,
185
+ borderRadius: 'var(--rads, 8px)',
186
+ color: 'var(--t1, #1a1a2e)',
187
+ fontFamily: 'var(--f, var(--sans, inherit))',
188
+ fontSize: `${s.fontSize}px`,
189
+ paddingTop: '8px',
190
+ paddingBottom: '8px',
191
+ paddingLeft: hasIconLeft ? '34px' : '12px',
192
+ paddingRight: (hasIconRight || showRightSlot) ? '34px' : '12px',
193
+ outline: 'none',
194
+ transition: 'border-color .14s, box-shadow .14s',
195
+ boxShadow: error
196
+ ? 'var(--dd, rgba(246,71,71,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
197
+ : focused
198
+ ? 'var(--pd, rgba(18,103,93,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
199
+ : 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
200
+ opacity: disabled ? 0.5 : 1,
201
+ cursor: disabled ? 'not-allowed' : undefined,
202
+ ...style,
203
+ }"
204
+ @input="handleInput"
205
+ @focus="focused = true; emit('focus', $event)"
206
+ @blur="focused = false; hovered = false; emit('blur', $event)"
207
+ />
208
+
209
+ <!-- Standard Input Control -->
210
+ <input
211
+ v-else
212
+ ref="inputRef"
213
+ :type="resolvedType"
214
+ :value="modelValue"
215
+ :placeholder="placeholder"
216
+ :disabled="disabled"
217
+ :class="['cynx-input-control', className]"
218
+ :style="{
219
+ display: 'block',
220
+ width: '100%',
221
+ background: disabled ? 'var(--grey-1, #f5f5f8)' : 'var(--input-bg, #fff)',
222
+ border: `1px solid ${error ? 'var(--danger, #f64747)' : focused ? 'var(--accent, #12675D)' : hovered ? 'var(--accent, #12675D)' : 'var(--bd, #d7d8e0)'}`,
223
+ borderRadius: 'var(--rads, 8px)',
224
+ color: 'var(--t1, #1a1a2e)',
225
+ fontFamily: 'var(--f, var(--sans, inherit))',
226
+ fontSize: `${s.fontSize}px`,
227
+ height: `${s.height}px`,
228
+ padding: `0 ${s.paddingH}px`,
229
+ paddingLeft: hasIconLeft ? '34px' : `${s.paddingH}px`,
230
+ paddingRight: (hasIconRight || showRightSlot) ? '34px' : `${s.paddingH}px`,
231
+ outline: 'none',
232
+ transition: 'border-color .14s, box-shadow .14s',
233
+ boxShadow: error
234
+ ? 'var(--dd, rgba(246,71,71,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
235
+ : focused
236
+ ? 'var(--pd, rgba(18,103,93,.12)), var(--shadow, 0 1px 3px rgba(20,22,41,.1))'
237
+ : 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
238
+ opacity: disabled ? 0.5 : 1,
239
+ cursor: disabled ? 'not-allowed' : undefined,
240
+ ...passwdStyle,
241
+ ...style,
242
+ }"
243
+ @input="handleInput"
244
+ @focus="focused = true; emit('focus', $event)"
245
+ @blur="focused = false; hovered = false; emit('blur', $event)"
246
+ />
247
+
248
+ <!-- Clear Button (1-to-1 matching Input.jsx) -->
249
+ <button
250
+ v-if="showClear"
251
+ type="button"
252
+ tabindex="-1"
253
+ style="
254
+ position: absolute;
255
+ right: 8px;
256
+ top: 50%;
257
+ transform: translateY(-50%);
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ width: 18px;
262
+ height: 18px;
263
+ border-radius: 50%;
264
+ background: transparent;
265
+ border: none;
266
+ cursor: pointer;
267
+ color: var(--t3, #9b9daa);
268
+ padding: 0;
269
+ line-height: 0;
270
+ z-index: 2;
271
+ "
272
+ title="Clear"
273
+ @click="handleClear"
274
+ >
275
+ <svg
276
+ width="10"
277
+ height="10"
278
+ viewBox="0 0 10 10"
279
+ fill="none"
280
+ stroke="currentColor"
281
+ stroke-width="1.8"
282
+ stroke-linecap="round"
283
+ >
284
+ <line x1="1" y1="1" x2="9" y2="9" />
285
+ <line x1="9" y1="1" x2="1" y2="9" />
286
+ </svg>
287
+ </button>
288
+
289
+ <!-- Suffix Text / Element -->
290
+ <span
291
+ v-if="hasSuffix && !showClear"
292
+ style="
293
+ position: absolute;
294
+ right: 8px;
295
+ top: 50%;
296
+ transform: translateY(-50%);
297
+ display: flex;
298
+ align-items: center;
299
+ gap: 4px;
300
+ pointer-events: none;
301
+ z-index: 2;
302
+ "
303
+ >
304
+ {{ suffix }}
305
+ </span>
306
+ </div>
307
+
308
+ <!-- Hint / Error Messages -->
309
+ <span
310
+ v-if="hint && !error"
311
+ style="font-size: 11px; color: var(--t3, #9b9daa);"
312
+ >
313
+ {{ hint }}
314
+ </span>
315
+ <span
316
+ v-if="error"
317
+ style="font-size: 11px; color: var(--danger, #f64747);"
318
+ >
319
+ {{ error }}
320
+ </span>
321
+ </div>
322
+ </template>
323
+
324
+ <style scoped>
325
+ .cynx-input-control::placeholder {
326
+ color: var(--t3, #9b9daa);
327
+ opacity: 1;
328
+ }
329
+
330
+ :root.dark .cynx-input-control,
331
+ :root.dark2 .cynx-input-control {
332
+ --input-bg: #141629;
333
+ --t1: #ffffff;
334
+ --bd: #334155;
335
+ }
336
+
337
+ :root.dark .cynx-input-control::placeholder,
338
+ :root.dark2 .cynx-input-control::placeholder {
339
+ color: #64748b;
340
+ }
341
+ </style>