@umbra.ui/core 0.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.
- package/dist/components/controls/Dropdown/types.d.ts +5 -0
- package/dist/components/controls/Dropdown/types.d.ts.map +1 -0
- package/dist/components/controls/Dropdown/types.js +1 -0
- package/dist/components/controls/SegmentedControl/types.d.ts +6 -0
- package/dist/components/controls/SegmentedControl/types.d.ts.map +1 -0
- package/dist/components/controls/SegmentedControl/types.js +1 -0
- package/dist/components/dialogs/Alert/types.d.ts +7 -0
- package/dist/components/dialogs/Alert/types.d.ts.map +1 -0
- package/dist/components/dialogs/Alert/types.js +1 -0
- package/dist/components/dialogs/Toast/types.d.ts +34 -0
- package/dist/components/dialogs/Toast/types.d.ts.map +1 -0
- package/dist/components/dialogs/Toast/types.js +10 -0
- package/dist/components/dialogs/Toast/useToast.d.ts +36 -0
- package/dist/components/dialogs/Toast/useToast.d.ts.map +1 -0
- package/dist/components/dialogs/Toast/useToast.js +90 -0
- package/dist/components/indicators/Tooltip/tooltip.d.ts +3 -0
- package/dist/components/indicators/Tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/tooltip.js +33 -0
- package/dist/components/indicators/Tooltip/types.d.ts +14 -0
- package/dist/components/indicators/Tooltip/types.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/types.js +1 -0
- package/dist/components/indicators/Tooltip/useTooltip.d.ts +18 -0
- package/dist/components/indicators/Tooltip/useTooltip.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/useTooltip.js +57 -0
- package/dist/components/inputs/Tags/tag-bar-styles.d.ts +14 -0
- package/dist/components/inputs/Tags/tag-bar-styles.d.ts.map +1 -0
- package/dist/components/inputs/Tags/tag-bar-styles.js +313 -0
- package/dist/components/inputs/Tags/types.d.ts +93 -0
- package/dist/components/inputs/Tags/types.d.ts.map +1 -0
- package/dist/components/inputs/Tags/types.js +216 -0
- package/dist/components/inputs/search/types.d.ts +9 -0
- package/dist/components/inputs/search/types.d.ts.map +1 -0
- package/dist/components/inputs/search/types.js +1 -0
- package/dist/components/navigation/adaptive/types.d.ts +16 -0
- package/dist/components/navigation/adaptive/types.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/types.js +1 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts +27 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.js +40 -0
- package/dist/components/navigation/adaptive/useBreakpoints.d.ts +6 -0
- package/dist/components/navigation/adaptive/useBreakpoints.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useBreakpoints.js +37 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.d.ts +93 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.js +145 -0
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts +31 -0
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useViewAnimation.js +591 -0
- package/dist/components/navigation/adaptive/useViewResize.d.ts +52 -0
- package/dist/components/navigation/adaptive/useViewResize.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useViewResize.js +146 -0
- package/dist/components/navigation/navstack/useNavigationStack.d.ts +25 -0
- package/dist/components/navigation/navstack/useNavigationStack.d.ts.map +1 -0
- package/dist/components/navigation/navstack/useNavigationStack.js +133 -0
- package/dist/components/navigation/slideover/useSlideoverController.d.ts +20 -0
- package/dist/components/navigation/slideover/useSlideoverController.d.ts.map +1 -0
- package/dist/components/navigation/slideover/useSlideoverController.js +267 -0
- package/dist/components/navigation/splitview/useSplitViewController.d.ts +20 -0
- package/dist/components/navigation/splitview/useSplitViewController.d.ts.map +1 -0
- package/dist/components/navigation/splitview/useSplitViewController.js +325 -0
- package/dist/components/navigation/tabcontroller/types.d.ts +21 -0
- package/dist/components/navigation/tabcontroller/types.d.ts.map +1 -0
- package/dist/components/navigation/tabcontroller/types.js +1 -0
- package/dist/components/navigation/tabcontroller/useTabController.d.ts +5 -0
- package/dist/components/navigation/tabcontroller/useTabController.d.ts.map +1 -0
- package/dist/components/navigation/tabcontroller/useTabController.js +10 -0
- package/dist/components/navigation/types.d.ts +8 -0
- package/dist/components/navigation/types.d.ts.map +1 -0
- package/dist/components/navigation/types.js +1 -0
- package/dist/components/pickers/CollectionPicker/types.d.ts +11 -0
- package/dist/components/pickers/CollectionPicker/types.d.ts.map +1 -0
- package/dist/components/pickers/CollectionPicker/types.js +1 -0
- package/dist/components/pickers/ColorPicker/colors.d.ts +13 -0
- package/dist/components/pickers/ColorPicker/colors.d.ts.map +1 -0
- package/dist/components/pickers/ColorPicker/colors.js +266 -0
- package/dist/components/pickers/FilePicker/types.d.ts +10 -0
- package/dist/components/pickers/FilePicker/types.d.ts.map +1 -0
- package/dist/components/pickers/FilePicker/types.js +1 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +196 -0
- package/dist/theme.d.ts +73 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +279 -0
- package/dist/themes/blank.d.ts +7 -0
- package/dist/themes/blank.d.ts.map +1 -0
- package/dist/themes/blank.js +543 -0
- package/dist/themes/crimson-dark.d.ts +4 -0
- package/dist/themes/crimson-dark.d.ts.map +1 -0
- package/dist/themes/crimson-dark.js +552 -0
- package/dist/themes/cyan-light.d.ts +4 -0
- package/dist/themes/cyan-light.d.ts.map +1 -0
- package/dist/themes/cyan-light.js +552 -0
- package/dist/themes/dark.d.ts +4 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/dark.js +551 -0
- package/dist/themes/gold-dark.d.ts +4 -0
- package/dist/themes/gold-dark.d.ts.map +1 -0
- package/dist/themes/gold-dark.js +552 -0
- package/dist/themes/grass-dark.d.ts +4 -0
- package/dist/themes/grass-dark.d.ts.map +1 -0
- package/dist/themes/grass-dark.js +552 -0
- package/dist/themes/indigo.d.ts +4 -0
- package/dist/themes/indigo.d.ts.map +1 -0
- package/dist/themes/indigo.js +552 -0
- package/dist/themes/light.d.ts +4 -0
- package/dist/themes/light.d.ts.map +1 -0
- package/dist/themes/light.js +551 -0
- package/dist/themes/orange-dark.d.ts +4 -0
- package/dist/themes/orange-dark.d.ts.map +1 -0
- package/dist/themes/orange-dark.js +551 -0
- package/dist/themes/orange-light.d.ts +4 -0
- package/dist/themes/orange-light.d.ts.map +1 -0
- package/dist/themes/orange-light.js +551 -0
- package/package.json +62 -0
- package/src/components/controls/Button/Button.vue +417 -0
- package/src/components/controls/Button/README.md +348 -0
- package/src/components/controls/Button/theme.css +200 -0
- package/src/components/controls/Checkbox/Checkbox.vue +164 -0
- package/src/components/controls/Checkbox/README.md +441 -0
- package/src/components/controls/Checkbox/theme.css +36 -0
- package/src/components/controls/Dropdown/Dropdown.vue +476 -0
- package/src/components/controls/Dropdown/README.md +370 -0
- package/src/components/controls/Dropdown/theme.css +50 -0
- package/src/components/controls/Dropdown/types.ts +6 -0
- package/src/components/controls/IconButton/IconButton.vue +267 -0
- package/src/components/controls/IconButton/README.md +502 -0
- package/src/components/controls/IconButton/theme.css +89 -0
- package/src/components/controls/Radio/README.md +591 -0
- package/src/components/controls/Radio/Radio.vue +89 -0
- package/src/components/controls/Radio/theme.css +14 -0
- package/src/components/controls/RangeSlider/README.md +608 -0
- package/src/components/controls/RangeSlider/RangeSlider.vue +535 -0
- package/src/components/controls/RangeSlider/theme.css +80 -0
- package/src/components/controls/SegmentedControl/README.md +587 -0
- package/src/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
- package/src/components/controls/SegmentedControl/theme.css +60 -0
- package/src/components/controls/SegmentedControl/types.ts +5 -0
- package/src/components/controls/Slider/README.md +627 -0
- package/src/components/controls/Slider/Slider.vue +260 -0
- package/src/components/controls/Slider/theme.css +74 -0
- package/src/components/controls/Stepper/README.md +601 -0
- package/src/components/controls/Stepper/Stepper.vue +103 -0
- package/src/components/controls/Stepper/theme.css +53 -0
- package/src/components/controls/Switch/README.md +667 -0
- package/src/components/controls/Switch/Switch.vue +127 -0
- package/src/components/controls/Switch/theme.css +42 -0
- package/src/components/dialogs/Alert/Alert.vue +218 -0
- package/src/components/dialogs/Alert/README.md +450 -0
- package/src/components/dialogs/Alert/theme.css +44 -0
- package/src/components/dialogs/Alert/types.ts +11 -0
- package/src/components/dialogs/Toast/README.md +522 -0
- package/src/components/dialogs/Toast/Toast.vue +296 -0
- package/src/components/dialogs/Toast/ToastContainer.vue +330 -0
- package/src/components/dialogs/Toast/theme.css +44 -0
- package/src/components/dialogs/Toast/types.ts +46 -0
- package/src/components/dialogs/Toast/useToast.ts +127 -0
- package/src/components/indicators/ProgressBar/ProgressBar.vue +98 -0
- package/src/components/indicators/ProgressBar/README.md +744 -0
- package/src/components/indicators/ProgressBar/theme.css +36 -0
- package/src/components/indicators/Tooltip/README.md +723 -0
- package/src/components/indicators/Tooltip/TooltipProvider.vue +142 -0
- package/src/components/indicators/Tooltip/theme.css +18 -0
- package/src/components/indicators/Tooltip/tooltip.ts +48 -0
- package/src/components/indicators/Tooltip/types.ts +15 -0
- package/src/components/indicators/Tooltip/useTooltip.ts +71 -0
- package/src/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
- package/src/components/inputs/AutogrowTextView/README.md +643 -0
- package/src/components/inputs/AutogrowTextView/theme.css +28 -0
- package/src/components/inputs/InputCard/InputCard.vue +600 -0
- package/src/components/inputs/InputCard/README.md +636 -0
- package/src/components/inputs/InputEmail/InputEmail.vue +698 -0
- package/src/components/inputs/InputEmail/README.md +764 -0
- package/src/components/inputs/InputNumber/InputNumber.vue +300 -0
- package/src/components/inputs/InputNumber/README.md +749 -0
- package/src/components/inputs/InputPhone/InputPhone.vue +645 -0
- package/src/components/inputs/InputPhone/README.md +636 -0
- package/src/components/inputs/InputSecure/InputSecure.vue +646 -0
- package/src/components/inputs/InputSecure/README.md +771 -0
- package/src/components/inputs/InputText/InputText.vue +225 -0
- package/src/components/inputs/InputText/README.md +844 -0
- package/src/components/inputs/OTP/OTP.vue +349 -0
- package/src/components/inputs/OTP/README.md +736 -0
- package/src/components/inputs/OTP/theme.css +50 -0
- package/src/components/inputs/StringCapture/README.md +718 -0
- package/src/components/inputs/StringCapture/StringCapture.vue +315 -0
- package/src/components/inputs/StringCapture/theme.css +86 -0
- package/src/components/inputs/Tags/README.md +897 -0
- package/src/components/inputs/Tags/TagBar.vue +793 -0
- package/src/components/inputs/Tags/TagCreation.vue +219 -0
- package/src/components/inputs/Tags/TagPicker.vue +380 -0
- package/src/components/inputs/Tags/tag-bar-styles.ts +354 -0
- package/src/components/inputs/Tags/theme.css +121 -0
- package/src/components/inputs/Tags/types.ts +346 -0
- package/src/components/inputs/search/README.md +759 -0
- package/src/components/inputs/search/SearchBar.vue +394 -0
- package/src/components/inputs/search/SearchResults.vue +310 -0
- package/src/components/inputs/search/theme.css +187 -0
- package/src/components/inputs/search/types.ts +8 -0
- package/src/components/inputs/theme.css +102 -0
- package/src/components/menus/ActionMenu/ActionMenu.vue +383 -0
- package/src/components/menus/ActionMenu/README.md +825 -0
- package/src/components/menus/ActionMenu/theme.css +93 -0
- package/src/components/models/Popover/Popover.vue +551 -0
- package/src/components/models/Popover/README.md +885 -0
- package/src/components/models/Popover/theme.css +52 -0
- package/src/components/models/Sheet/README.md +1159 -0
- package/src/components/models/Sheet/Sheet.vue +465 -0
- package/src/components/models/Sheet/theme.css +72 -0
- package/src/components/models/Sidebar/README.md +1228 -0
- package/src/components/models/Sidebar/Sidebar.vue +480 -0
- package/src/components/models/Sidebar/theme.css +90 -0
- package/src/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
- package/src/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
- package/src/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
- package/src/components/navigation/adaptive/README.md +768 -0
- package/src/components/navigation/adaptive/types.ts +19 -0
- package/src/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
- package/src/components/navigation/adaptive/useBreakpoints.ts +41 -0
- package/src/components/navigation/adaptive/useContainerMonitor.ts +214 -0
- package/src/components/navigation/adaptive/useViewAnimation.ts +721 -0
- package/src/components/navigation/adaptive/useViewResize.ts +211 -0
- package/src/components/navigation/navstack/NavigationStack.vue +180 -0
- package/src/components/navigation/navstack/README.md +994 -0
- package/src/components/navigation/navstack/useNavigationStack.ts +164 -0
- package/src/components/navigation/slideover/README.md +1275 -0
- package/src/components/navigation/slideover/SlideoverController.vue +287 -0
- package/src/components/navigation/slideover/useSlideoverController.ts +320 -0
- package/src/components/navigation/splitview/README.md +1115 -0
- package/src/components/navigation/splitview/SplitViewController.vue +176 -0
- package/src/components/navigation/splitview/useSplitViewController.ts +388 -0
- package/src/components/navigation/tabcontroller/README.md +919 -0
- package/src/components/navigation/tabcontroller/TabController.vue +307 -0
- package/src/components/navigation/tabcontroller/TabItem.vue +57 -0
- package/src/components/navigation/tabcontroller/types.ts +24 -0
- package/src/components/navigation/tabcontroller/useTabController.ts +18 -0
- package/src/components/navigation/theme.css +91 -0
- package/src/components/navigation/types.ts +7 -0
- package/src/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
- package/src/components/pickers/CollectionPicker/README.md +1115 -0
- package/src/components/pickers/CollectionPicker/theme.css +14 -0
- package/src/components/pickers/CollectionPicker/types.ts +11 -0
- package/src/components/pickers/ColorPicker/ColorPicker.vue +376 -0
- package/src/components/pickers/ColorPicker/README.md +1439 -0
- package/src/components/pickers/ColorPicker/colors.ts +299 -0
- package/src/components/pickers/ColorPicker/theme.css +32 -0
- package/src/components/pickers/DatePicker/DatePicker.vue +660 -0
- package/src/components/pickers/DatePicker/README.md +1195 -0
- package/src/components/pickers/DatePicker/theme.css +22 -0
- package/src/components/pickers/FilePicker/FilePicker.vue +534 -0
- package/src/components/pickers/FilePicker/README.md +1542 -0
- package/src/components/pickers/FilePicker/theme.css +48 -0
- package/src/components/pickers/FilePicker/types.ts +10 -0
- package/src/components/pickers/IconPicker/IconPicker.vue +327 -0
- package/src/components/pickers/IconPicker/README.md +1161 -0
- package/src/components/pickers/IconPicker/theme.css +28 -0
- package/src/components/pickers/theme.css +82 -0
- package/src/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
- package/src/components/views/MarkdownViewer/README.md +833 -0
- package/src/components/views/MarkdownViewer/theme.css +130 -0
- package/src/index.ts +263 -0
- package/src/theme.ts +378 -0
- package/src/themes/crimson-dark.ts +556 -0
- package/src/themes/cyan-light.ts +556 -0
- package/src/themes/dark.ts +557 -0
- package/src/themes/gold-dark.ts +556 -0
- package/src/themes/grass-dark.ts +556 -0
- package/src/themes/indigo.ts +556 -0
- package/src/themes/light.ts +557 -0
- package/src/themes/orange-dark.ts +557 -0
- package/src/themes/orange-light.ts +557 -0
- package/src/vue.d.ts +45 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, computed } from "vue";
|
|
3
|
+
import "../theme.css";
|
|
4
|
+
import {
|
|
5
|
+
PhoneIcon,
|
|
6
|
+
TriangleWarningIcon,
|
|
7
|
+
CircleCheckIcon,
|
|
8
|
+
} from "@umbra-ui/icons";
|
|
9
|
+
|
|
10
|
+
export interface Props {
|
|
11
|
+
value?: string;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
defaultCountry?: string; // ISO country code (e.g., 'US', 'GB')
|
|
14
|
+
allowInternational?: boolean;
|
|
15
|
+
showCountryCode?: boolean; // Whether to show country code when detected
|
|
16
|
+
state?: "normal" | "active" | "disabled" | "readonly" | "error";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
20
|
+
value: "",
|
|
21
|
+
placeholder: "Enter phone number",
|
|
22
|
+
defaultCountry: "US",
|
|
23
|
+
allowInternational: true,
|
|
24
|
+
showCountryCode: true,
|
|
25
|
+
state: "normal",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
"update:value": [value: string];
|
|
30
|
+
"update:formatted": [value: string];
|
|
31
|
+
"update:valid": [value: boolean];
|
|
32
|
+
}>();
|
|
33
|
+
|
|
34
|
+
const internalValue = ref(props.value);
|
|
35
|
+
const cursorPosition = ref<number | null>(null);
|
|
36
|
+
const inputRef = ref<HTMLInputElement | null>(null);
|
|
37
|
+
|
|
38
|
+
// Add this computed property to track current digit count
|
|
39
|
+
const currentDigitCount = computed(() => {
|
|
40
|
+
return internalValue.value.replace(/\D/g, "").length;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Add this computed property for max allowed digits
|
|
44
|
+
const maxAllowedDigits = computed(() => {
|
|
45
|
+
const cleaned = internalValue.value.replace(/\D/g, "");
|
|
46
|
+
const country = detectCountry(cleaned);
|
|
47
|
+
|
|
48
|
+
if (country === "US") {
|
|
49
|
+
return cleaned.startsWith("1") ? 11 : 10;
|
|
50
|
+
} else if (country === "GB") {
|
|
51
|
+
return cleaned.startsWith("44") ? 12 : 11;
|
|
52
|
+
}
|
|
53
|
+
return 15; // International max
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Country code patterns and formatting rules
|
|
57
|
+
const countryFormats = {
|
|
58
|
+
US: {
|
|
59
|
+
pattern: /^(\+?1)?[\s.-]?\(?(\d{3})\)?[\s.-]?(\d{3})[\s.-]?(\d{4})$/,
|
|
60
|
+
format: (match: RegExpMatchArray) => {
|
|
61
|
+
const countryCode = match[1];
|
|
62
|
+
const areaCode = match[2];
|
|
63
|
+
const prefix = match[3];
|
|
64
|
+
const lineNumber = match[4];
|
|
65
|
+
if (countryCode) {
|
|
66
|
+
return `+1 (${areaCode}) ${prefix}-${lineNumber}`;
|
|
67
|
+
}
|
|
68
|
+
return `(${areaCode}) ${prefix}-${lineNumber}`;
|
|
69
|
+
},
|
|
70
|
+
placeholder: "(555) 123-4567",
|
|
71
|
+
maxLength: 14,
|
|
72
|
+
countryCode: "1",
|
|
73
|
+
},
|
|
74
|
+
GB: {
|
|
75
|
+
pattern: /^(\+?44)?[\s.-]?(\d{4,5})[\s.-]?(\d{6})$/,
|
|
76
|
+
format: (match: RegExpMatchArray) => {
|
|
77
|
+
const countryCode = match[1];
|
|
78
|
+
const prefix = match[2];
|
|
79
|
+
const number = match[3];
|
|
80
|
+
if (countryCode) {
|
|
81
|
+
return `+44 ${prefix} ${number}`;
|
|
82
|
+
}
|
|
83
|
+
return `${prefix} ${number}`;
|
|
84
|
+
},
|
|
85
|
+
placeholder: "20 1234 5678",
|
|
86
|
+
maxLength: 13,
|
|
87
|
+
countryCode: "44",
|
|
88
|
+
},
|
|
89
|
+
// Add more country formats as needed
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Detect country from number
|
|
93
|
+
const detectCountry = (number: string): string => {
|
|
94
|
+
const cleaned = number.replace(/\D/g, "");
|
|
95
|
+
|
|
96
|
+
// Check for country codes at the beginning
|
|
97
|
+
if (
|
|
98
|
+
cleaned.startsWith("1") &&
|
|
99
|
+
(cleaned.length === 11 || cleaned.length > 10)
|
|
100
|
+
) {
|
|
101
|
+
return "US"; // US with country code
|
|
102
|
+
}
|
|
103
|
+
if (cleaned.startsWith("44")) {
|
|
104
|
+
return "GB";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Default to US for 10-digit numbers or shorter
|
|
108
|
+
if (cleaned.length <= 10) {
|
|
109
|
+
return "US"; // US without country code
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return props.defaultCountry;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Format phone number based on country
|
|
116
|
+
const formatPhoneNumber = (
|
|
117
|
+
value: string
|
|
118
|
+
): { formatted: string; cursorPos: number } => {
|
|
119
|
+
// Ensure we only work with digits
|
|
120
|
+
let cleaned = value.replace(/\D/g, "");
|
|
121
|
+
|
|
122
|
+
if (cleaned.length === 0) return { formatted: "", cursorPos: 0 };
|
|
123
|
+
|
|
124
|
+
const country = detectCountry(cleaned);
|
|
125
|
+
let formatted = "";
|
|
126
|
+
|
|
127
|
+
// For US numbers
|
|
128
|
+
if (country === "US") {
|
|
129
|
+
// Check if number starts with country code 1
|
|
130
|
+
if (cleaned.startsWith("1") && cleaned.length > 10) {
|
|
131
|
+
// Has country code - strictly limit to 11 digits total
|
|
132
|
+
const validDigits = cleaned.slice(0, 11);
|
|
133
|
+
const withoutCountryCode = validDigits.slice(1);
|
|
134
|
+
|
|
135
|
+
formatted = "+1 ";
|
|
136
|
+
for (let i = 0; i < withoutCountryCode.length && i < 10; i++) {
|
|
137
|
+
if (i === 0) formatted += "(";
|
|
138
|
+
if (i === 3) formatted += ") ";
|
|
139
|
+
if (i === 6) formatted += "-";
|
|
140
|
+
formatted += withoutCountryCode[i];
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
// No country code - strictly limit to 10 digits
|
|
144
|
+
const validDigits = cleaned.slice(0, 10);
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < validDigits.length; i++) {
|
|
147
|
+
if (i === 0) formatted += "(";
|
|
148
|
+
if (i === 3) formatted += ") ";
|
|
149
|
+
if (i === 6) formatted += "-";
|
|
150
|
+
formatted += validDigits[i];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} else if (country === "GB") {
|
|
154
|
+
// UK formatting - limit digits appropriately
|
|
155
|
+
if (cleaned.startsWith("44")) {
|
|
156
|
+
const validDigits = cleaned.slice(0, 12); // 44 + 10 digits
|
|
157
|
+
formatted = "+44 ";
|
|
158
|
+
const withoutCountryCode = validDigits.slice(2);
|
|
159
|
+
|
|
160
|
+
// Format UK number (various formats exist, this is simplified)
|
|
161
|
+
for (let i = 0; i < withoutCountryCode.length; i++) {
|
|
162
|
+
if (i === 4) formatted += " ";
|
|
163
|
+
formatted += withoutCountryCode[i];
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
const validDigits = cleaned.slice(0, 11);
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < validDigits.length; i++) {
|
|
169
|
+
if (i === 5) formatted += " ";
|
|
170
|
+
formatted += validDigits[i];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
// Generic international formatting - respect E.164 max length
|
|
175
|
+
const validDigits = cleaned.slice(0, 15);
|
|
176
|
+
|
|
177
|
+
if (validDigits.length > 10) {
|
|
178
|
+
// Assume first 1-3 digits are country code
|
|
179
|
+
const possibleCountryCodeLength = validDigits.startsWith("1")
|
|
180
|
+
? 1
|
|
181
|
+
: validDigits.startsWith("44")
|
|
182
|
+
? 2
|
|
183
|
+
: validDigits.startsWith("86")
|
|
184
|
+
? 2
|
|
185
|
+
: 3;
|
|
186
|
+
formatted = `+${validDigits.slice(
|
|
187
|
+
0,
|
|
188
|
+
possibleCountryCodeLength
|
|
189
|
+
)} ${validDigits.slice(possibleCountryCodeLength)}`;
|
|
190
|
+
} else {
|
|
191
|
+
formatted = validDigits;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Calculate cursor position
|
|
196
|
+
const cursorPos = formatted.length;
|
|
197
|
+
|
|
198
|
+
return { formatted, cursorPos };
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Validate phone number
|
|
202
|
+
const isValidPhoneNumber = (value: string): boolean => {
|
|
203
|
+
const cleaned = value.replace(/\D/g, "");
|
|
204
|
+
if (cleaned.length === 0) return true; // Empty is valid
|
|
205
|
+
|
|
206
|
+
const country = detectCountry(cleaned);
|
|
207
|
+
|
|
208
|
+
if (country === "US") {
|
|
209
|
+
// Valid US numbers: EXACTLY 10 digits or EXACTLY 11 digits starting with 1
|
|
210
|
+
if (cleaned.length === 10) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (cleaned.length === 11 && cleaned.startsWith("1")) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
return false; // Any other length is invalid
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (country === "GB") {
|
|
220
|
+
// Valid UK numbers: typically 11 digits (including country code 44)
|
|
221
|
+
if (cleaned.startsWith("44")) {
|
|
222
|
+
return cleaned.length === 12; // 44 + 10 digits
|
|
223
|
+
}
|
|
224
|
+
return cleaned.length === 11; // Without country code
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// General international validation
|
|
228
|
+
return cleaned.length >= 7 && cleaned.length <= 15; // ITU-T E.164 standard
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Computed properties
|
|
232
|
+
const isValid = computed(() => isValidPhoneNumber(internalValue.value));
|
|
233
|
+
const currentPlaceholder = computed(() => {
|
|
234
|
+
const country = detectCountry(internalValue.value);
|
|
235
|
+
const format = countryFormats[country as keyof typeof countryFormats];
|
|
236
|
+
return format?.placeholder || props.placeholder;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Override state to show error when phone number is invalid
|
|
240
|
+
const effectiveState = computed(() => {
|
|
241
|
+
if (!isValid.value && internalValue.value.length > 0) {
|
|
242
|
+
return "error";
|
|
243
|
+
}
|
|
244
|
+
return props.state;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Get the appropriate text color for icons based on state
|
|
248
|
+
const getIconColor = () => {
|
|
249
|
+
return "var(--input-text-filled)";
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Watch for changes to value prop
|
|
253
|
+
watch(
|
|
254
|
+
() => props.value,
|
|
255
|
+
(newValue) => {
|
|
256
|
+
if (newValue !== internalValue.value) {
|
|
257
|
+
const { formatted } = formatPhoneNumber(newValue);
|
|
258
|
+
internalValue.value = formatted;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// Update handleBeforeInput to prevent typing when at max digits
|
|
264
|
+
const handleBeforeInput = (event: Event) => {
|
|
265
|
+
const inputEvent = event as InputEvent;
|
|
266
|
+
const data = inputEvent.data;
|
|
267
|
+
|
|
268
|
+
// Allow deletion operations
|
|
269
|
+
if (!data) return;
|
|
270
|
+
|
|
271
|
+
// Check if we're at max digits
|
|
272
|
+
if (currentDigitCount.value >= maxAllowedDigits.value) {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Only allow digits - NO other characters
|
|
278
|
+
const isDigit = /^[0-9]+$/;
|
|
279
|
+
if (!isDigit.test(data)) {
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// Also update the handleInput function to prevent extra digits
|
|
285
|
+
const handleInput = (event: Event) => {
|
|
286
|
+
const target = event.target as HTMLInputElement;
|
|
287
|
+
const rawValue = target.value;
|
|
288
|
+
const caretPos = target.selectionStart || 0;
|
|
289
|
+
|
|
290
|
+
// Strip out any non-allowed characters
|
|
291
|
+
let digitsOnly = rawValue.replace(/[^\d]/g, "");
|
|
292
|
+
|
|
293
|
+
// Determine maximum allowed digits based on country
|
|
294
|
+
const country = detectCountry(digitsOnly);
|
|
295
|
+
let maxDigits = 15; // Default international max
|
|
296
|
+
|
|
297
|
+
if (country === "US") {
|
|
298
|
+
// For US: 10 digits without country code, 11 with country code
|
|
299
|
+
if (digitsOnly.startsWith("1")) {
|
|
300
|
+
maxDigits = 11;
|
|
301
|
+
} else {
|
|
302
|
+
maxDigits = 10;
|
|
303
|
+
}
|
|
304
|
+
} else if (country === "GB") {
|
|
305
|
+
// For UK: 11 without country code, 12 with country code
|
|
306
|
+
if (digitsOnly.startsWith("44")) {
|
|
307
|
+
maxDigits = 12;
|
|
308
|
+
} else {
|
|
309
|
+
maxDigits = 11;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Enforce the digit limit
|
|
314
|
+
digitsOnly = digitsOnly.slice(0, maxDigits);
|
|
315
|
+
|
|
316
|
+
// Format the number
|
|
317
|
+
const { formatted, cursorPos } = formatPhoneNumber(digitsOnly);
|
|
318
|
+
internalValue.value = formatted;
|
|
319
|
+
|
|
320
|
+
// Update cursor position after formatting
|
|
321
|
+
cursorPosition.value = cursorPos;
|
|
322
|
+
|
|
323
|
+
// Emit events
|
|
324
|
+
emit("update:value", formatted);
|
|
325
|
+
emit("update:formatted", formatted);
|
|
326
|
+
emit("update:valid", isValidPhoneNumber(formatted));
|
|
327
|
+
|
|
328
|
+
// Set cursor position after Vue updates the DOM
|
|
329
|
+
requestAnimationFrame(() => {
|
|
330
|
+
if (inputRef.value && cursorPosition.value !== null) {
|
|
331
|
+
inputRef.value.setSelectionRange(
|
|
332
|
+
cursorPosition.value,
|
|
333
|
+
cursorPosition.value
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// Handle paste
|
|
340
|
+
const handlePaste = (event: ClipboardEvent) => {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
const pastedText = event.clipboardData?.getData("text") || "";
|
|
343
|
+
let cleanedNumber = pastedText.replace(/\D/g, "");
|
|
344
|
+
|
|
345
|
+
if (cleanedNumber) {
|
|
346
|
+
// Determine maximum allowed digits based on country
|
|
347
|
+
const country = detectCountry(cleanedNumber);
|
|
348
|
+
let maxDigits = 15; // Default international max
|
|
349
|
+
|
|
350
|
+
if (country === "US") {
|
|
351
|
+
// For US: 10 digits without country code, 11 with country code
|
|
352
|
+
if (cleanedNumber.startsWith("1")) {
|
|
353
|
+
maxDigits = 11;
|
|
354
|
+
} else {
|
|
355
|
+
maxDigits = 10;
|
|
356
|
+
}
|
|
357
|
+
} else if (country === "GB") {
|
|
358
|
+
// For UK: 11 without country code, 12 with country code
|
|
359
|
+
if (cleanedNumber.startsWith("44")) {
|
|
360
|
+
maxDigits = 12;
|
|
361
|
+
} else {
|
|
362
|
+
maxDigits = 11;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Enforce the digit limit BEFORE formatting
|
|
367
|
+
cleanedNumber = cleanedNumber.slice(0, maxDigits);
|
|
368
|
+
|
|
369
|
+
const { formatted } = formatPhoneNumber(cleanedNumber);
|
|
370
|
+
internalValue.value = formatted;
|
|
371
|
+
emit("update:value", formatted);
|
|
372
|
+
emit("update:formatted", formatted);
|
|
373
|
+
emit("update:valid", isValidPhoneNumber(formatted));
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// Update handleKeyDown to also check digit limit
|
|
378
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
379
|
+
const target = event.target as HTMLInputElement;
|
|
380
|
+
const key = event.key;
|
|
381
|
+
const caretPos = target.selectionStart || 0;
|
|
382
|
+
|
|
383
|
+
// Block any non-digit keys except control keys
|
|
384
|
+
const isControlKey = [
|
|
385
|
+
"Backspace",
|
|
386
|
+
"Delete",
|
|
387
|
+
"Tab",
|
|
388
|
+
"Escape",
|
|
389
|
+
"Enter",
|
|
390
|
+
"ArrowLeft",
|
|
391
|
+
"ArrowRight",
|
|
392
|
+
"ArrowUp",
|
|
393
|
+
"ArrowDown",
|
|
394
|
+
].includes(key);
|
|
395
|
+
const isModifierKey = event.ctrlKey || event.metaKey || event.altKey;
|
|
396
|
+
|
|
397
|
+
// If it's a digit and we're at max, prevent it
|
|
398
|
+
if (
|
|
399
|
+
/^[0-9]$/.test(key) &&
|
|
400
|
+
!isModifierKey &&
|
|
401
|
+
currentDigitCount.value >= maxAllowedDigits.value
|
|
402
|
+
) {
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!isControlKey && !isModifierKey && !/^[0-9]$/.test(key)) {
|
|
408
|
+
event.preventDefault();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Allow backspace to delete formatting characters
|
|
413
|
+
if (key === "Backspace" && caretPos > 0) {
|
|
414
|
+
const charToDelete = internalValue.value[caretPos - 1];
|
|
415
|
+
if (["(", ")", " ", "-"].includes(charToDelete)) {
|
|
416
|
+
event.preventDefault();
|
|
417
|
+
target.setSelectionRange(caretPos - 1, caretPos - 1);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
const getPlaceholder = computed(() => {
|
|
422
|
+
if (props.state === "readonly") {
|
|
423
|
+
return "Field Cannot Be Edited";
|
|
424
|
+
}
|
|
425
|
+
return currentPlaceholder.value;
|
|
426
|
+
});
|
|
427
|
+
</script>
|
|
428
|
+
|
|
429
|
+
<template>
|
|
430
|
+
<div :class="$style.container">
|
|
431
|
+
<div :class="[$style.input_container, $style[effectiveState]]">
|
|
432
|
+
<input
|
|
433
|
+
ref="inputRef"
|
|
434
|
+
class="body"
|
|
435
|
+
type="tel"
|
|
436
|
+
:placeholder="getPlaceholder"
|
|
437
|
+
:value="internalValue"
|
|
438
|
+
@beforeinput="handleBeforeInput"
|
|
439
|
+
@input="handleInput"
|
|
440
|
+
@paste="handlePaste"
|
|
441
|
+
@keydown="handleKeyDown"
|
|
442
|
+
:maxlength="20"
|
|
443
|
+
:disabled="state === 'disabled'"
|
|
444
|
+
:readonly="state === 'readonly'"
|
|
445
|
+
/>
|
|
446
|
+
<PhoneIcon v-if="!internalValue" :size="16" />
|
|
447
|
+
<transition name="fade">
|
|
448
|
+
<div
|
|
449
|
+
v-if="!isValid && internalValue.length > 0"
|
|
450
|
+
:class="$style.error_icon"
|
|
451
|
+
>
|
|
452
|
+
<TriangleWarningIcon size="16" />
|
|
453
|
+
</div>
|
|
454
|
+
</transition>
|
|
455
|
+
</div>
|
|
456
|
+
<transition name="slide-fade">
|
|
457
|
+
<p
|
|
458
|
+
v-if="!isValid && internalValue.length > 0"
|
|
459
|
+
:class="[$style.error_message, 'footnote']"
|
|
460
|
+
>
|
|
461
|
+
Please enter a valid phone number
|
|
462
|
+
</p>
|
|
463
|
+
</transition>
|
|
464
|
+
</div>
|
|
465
|
+
</template>
|
|
466
|
+
|
|
467
|
+
<style module>
|
|
468
|
+
.container {
|
|
469
|
+
display: flex;
|
|
470
|
+
flex-direction: column;
|
|
471
|
+
gap: 0.588rem;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.input_container {
|
|
475
|
+
width: 100%;
|
|
476
|
+
padding-left: 0.882rem;
|
|
477
|
+
padding-right: 0.882rem;
|
|
478
|
+
padding-top: 0.588rem;
|
|
479
|
+
padding-bottom: 0.588rem;
|
|
480
|
+
border: var(--input-border);
|
|
481
|
+
border-radius: var(--input-border-radius);
|
|
482
|
+
transition: background-color 0.3s ease, border 0.3s ease;
|
|
483
|
+
display: flex;
|
|
484
|
+
align-items: center;
|
|
485
|
+
justify-content: space-between;
|
|
486
|
+
position: relative;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.input_container:focus-within {
|
|
490
|
+
border: 1px solid var(--input-focus-border);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.input_container input {
|
|
494
|
+
border: none;
|
|
495
|
+
background: transparent;
|
|
496
|
+
outline: none;
|
|
497
|
+
width: 100%;
|
|
498
|
+
font-family: inherit;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* State-based styling using CSS variables */
|
|
502
|
+
.input_container.normal:has(input:placeholder-shown),
|
|
503
|
+
.input_container.active:has(input:placeholder-shown) {
|
|
504
|
+
background-color: var(--input-background-normal);
|
|
505
|
+
color: var(--input-text-empty);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.input_container.normal:has(input:not(:placeholder-shown)),
|
|
509
|
+
.input_container.active:has(input:not(:placeholder-shown)) {
|
|
510
|
+
background-color: var(--input-background-filled);
|
|
511
|
+
color: var(--input-text-filled);
|
|
512
|
+
border-color: var(--input-border-filled);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.input_container.normal input:placeholder-shown,
|
|
516
|
+
.input_container.active input:placeholder-shown {
|
|
517
|
+
color: var(--input-text-empty);
|
|
518
|
+
opacity: 0.6;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.input_container.normal input:not(:placeholder-shown),
|
|
522
|
+
.input_container.active input:not(:placeholder-shown) {
|
|
523
|
+
color: var(--input-text-filled);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.input_container.disabled:has(input:placeholder-shown),
|
|
527
|
+
.input_container.disabled:has(input:not(:placeholder-shown)) {
|
|
528
|
+
background-color: var(--input-disabled-bg);
|
|
529
|
+
color: var(--input-disabled-text);
|
|
530
|
+
border-color: var(--input-disabled-border);
|
|
531
|
+
cursor: not-allowed;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.input_container.disabled input:placeholder-shown,
|
|
535
|
+
.input_container.disabled input:not(:placeholder-shown) {
|
|
536
|
+
color: var(--input-disabled-text);
|
|
537
|
+
cursor: not-allowed;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.input_container.readonly:has(input:placeholder-shown),
|
|
541
|
+
.input_container.readonly:has(input:not(:placeholder-shown)) {
|
|
542
|
+
background-color: var(--input-readonly-bg);
|
|
543
|
+
color: var(--input-readonly-text);
|
|
544
|
+
border-color: var(--input-readonly-border);
|
|
545
|
+
cursor: not-allowed;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.input_container.readonly input:placeholder-shown,
|
|
549
|
+
.input_container.readonly input:not(:placeholder-shown) {
|
|
550
|
+
color: var(--input-readonly-text);
|
|
551
|
+
cursor: not-allowed;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.input_container.error:has(input:placeholder-shown),
|
|
555
|
+
.input_container.error:has(input:not(:placeholder-shown)) {
|
|
556
|
+
background-color: var(--input-error-bg);
|
|
557
|
+
color: var(--input-error-text);
|
|
558
|
+
border-color: var(--input-error-border);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.input_container.error input:placeholder-shown,
|
|
562
|
+
.input_container.error input:not(:placeholder-shown) {
|
|
563
|
+
color: var(--input-error-text);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.input_container.active {
|
|
567
|
+
pointer-events: none;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.error_icon {
|
|
571
|
+
color: var(--input-text-error);
|
|
572
|
+
display: flex;
|
|
573
|
+
align-items: center;
|
|
574
|
+
animation: shake 0.3s ease-in-out;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.error_message {
|
|
578
|
+
color: var(--input-text-error);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/* Animations */
|
|
582
|
+
@keyframes shake {
|
|
583
|
+
0%,
|
|
584
|
+
100% {
|
|
585
|
+
transform: translateX(0);
|
|
586
|
+
}
|
|
587
|
+
25% {
|
|
588
|
+
transform: translateX(-5px);
|
|
589
|
+
}
|
|
590
|
+
75% {
|
|
591
|
+
transform: translateX(5px);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.fade-enter-active,
|
|
596
|
+
.fade-leave-active {
|
|
597
|
+
transition: opacity 0.3s ease;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.fade-enter-from,
|
|
601
|
+
.fade-leave-to {
|
|
602
|
+
opacity: 0;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.slide-fade-enter-active {
|
|
606
|
+
transition: all 0.3s ease;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.slide-fade-leave-active {
|
|
610
|
+
transition: all 0.2s ease;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.slide-fade-enter-from {
|
|
614
|
+
transform: translateY(-10px);
|
|
615
|
+
opacity: 0;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.slide-fade-leave-to {
|
|
619
|
+
transform: translateY(-10px);
|
|
620
|
+
opacity: 0;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.input_container input::placeholder {
|
|
624
|
+
color: var(--input-placeholder);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.input_container:has(input:not(:placeholder-shown)) input::placeholder {
|
|
628
|
+
color: var(--input-placeholder-filled);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.input_container.readonly input::placeholder {
|
|
632
|
+
color: var(--input-readonly-placeholder);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.input_container.disabled input::placeholder {
|
|
636
|
+
color: var(--input-disabled-placeholder);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/* Responsive adjustments */
|
|
640
|
+
@media (max-width: 480px) {
|
|
641
|
+
.input_container input {
|
|
642
|
+
font-size: 16px; /* Prevents zoom on iOS */
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
</style>
|