@joewinke/jatui 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/package.json +46 -0
- package/src/lib/components/AudioWaveform.svelte +694 -0
- package/src/lib/components/AvailabilityModal.svelte +173 -0
- package/src/lib/components/Badge.svelte +38 -0
- package/src/lib/components/BookingForm.svelte +276 -0
- package/src/lib/components/Button.svelte +72 -0
- package/src/lib/components/CalendarPicker.svelte +284 -0
- package/src/lib/components/Card.svelte +67 -0
- package/src/lib/components/CharacterCounter.svelte +82 -0
- package/src/lib/components/ChipInput.svelte +596 -0
- package/src/lib/components/ColorSelector.svelte +163 -0
- package/src/lib/components/ConfirmModal.svelte +75 -0
- package/src/lib/components/CountdownTimer.svelte +94 -0
- package/src/lib/components/DateRangePicker.svelte +192 -0
- package/src/lib/components/Drawer.svelte +110 -0
- package/src/lib/components/FilterDropdown.svelte +202 -0
- package/src/lib/components/ImageUpload.svelte +97 -0
- package/src/lib/components/InlineEdit.svelte +283 -0
- package/src/lib/components/LazyImage.svelte +122 -0
- package/src/lib/components/LoadingSpinner.svelte +102 -0
- package/src/lib/components/Modal.svelte +208 -0
- package/src/lib/components/PhoneInput.svelte +92 -0
- package/src/lib/components/ResizableDivider.svelte +305 -0
- package/src/lib/components/ResizablePanel.svelte +302 -0
- package/src/lib/components/SearchDropdown.svelte +341 -0
- package/src/lib/components/SelectInput.svelte +215 -0
- package/src/lib/components/SignaturePad.svelte +171 -0
- package/src/lib/components/SortDropdown.svelte +148 -0
- package/src/lib/components/Sparkline.svelte +107 -0
- package/src/lib/components/SpeechForm.svelte +114 -0
- package/src/lib/components/StatusBadge.svelte +155 -0
- package/src/lib/components/TextArea.svelte +143 -0
- package/src/lib/components/TextInput.svelte +108 -0
- package/src/lib/components/ThemeSelector.svelte +195 -0
- package/src/lib/components/TimeSlotPicker.svelte +162 -0
- package/src/lib/components/VoicePlayer.svelte +420 -0
- package/src/lib/components/messaging/Avatar.svelte +81 -0
- package/src/lib/components/messaging/ChannelInfoModal.svelte +163 -0
- package/src/lib/components/messaging/ChannelList.svelte +107 -0
- package/src/lib/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
- package/src/lib/components/messaging/ChannelMembersModal.svelte +182 -0
- package/src/lib/components/messaging/CreateChannelModal.svelte +190 -0
- package/src/lib/components/messaging/DirectMessageList.svelte +145 -0
- package/src/lib/components/messaging/EmojiSelector.svelte +260 -0
- package/src/lib/components/messaging/MentionAutocomplete.svelte +193 -0
- package/src/lib/components/messaging/MessageAttachment.svelte +270 -0
- package/src/lib/components/messaging/MessageAttachmentUpload.svelte +243 -0
- package/src/lib/components/messaging/MessageInput.svelte +451 -0
- package/src/lib/components/messaging/MessageItem.svelte +338 -0
- package/src/lib/components/messaging/MessageThread.svelte +306 -0
- package/src/lib/components/messaging/NotificationSettingsModal.svelte +234 -0
- package/src/lib/components/messaging/QuotedMessageDisplay.svelte +118 -0
- package/src/lib/components/messaging/StartDMModal.svelte +100 -0
- package/src/lib/components/messaging/ThreadPanel.svelte +153 -0
- package/src/lib/index.ts +185 -0
- package/src/lib/types/booking.ts +143 -0
- package/src/lib/types/messaging.ts +459 -0
- package/src/lib/utils/currency.ts +20 -0
- package/src/lib/utils/daisyuiColors.ts +243 -0
- package/src/lib/utils/dateFormatters.ts +153 -0
- package/src/lib/utils/mentionParser.ts +188 -0
- package/src/lib/utils/phoneFormat.ts +74 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date formatting utilities for consistent date/time display.
|
|
3
|
+
*
|
|
4
|
+
* UTC/ISO Timestamp Handling:
|
|
5
|
+
* Database timestamps are often stored without timezone info (e.g., "2024-11-21 15:30:00").
|
|
6
|
+
* These utilities normalize timestamps to ensure correct UTC parsing.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Normalize a timestamp string to proper ISO 8601 format.
|
|
11
|
+
* Handles database timestamps that may lack 'T' separator or 'Z' suffix.
|
|
12
|
+
*/
|
|
13
|
+
export function normalizeTimestamp(timestamp: string): string {
|
|
14
|
+
if (!timestamp) return timestamp;
|
|
15
|
+
|
|
16
|
+
if (timestamp.includes('T')) {
|
|
17
|
+
if (timestamp.endsWith('Z')) return timestamp;
|
|
18
|
+
if (/[+-]\d{2}:\d{2}$/.test(timestamp)) return timestamp;
|
|
19
|
+
return timestamp + 'Z';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return timestamp.replace(' ', 'T') + 'Z';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse a potentially non-standard timestamp into a Date object.
|
|
27
|
+
*/
|
|
28
|
+
export function parseTimestamp(timestamp: string | null | undefined): Date | null {
|
|
29
|
+
if (!timestamp) return null;
|
|
30
|
+
|
|
31
|
+
const normalized = normalizeTimestamp(timestamp);
|
|
32
|
+
const date = new Date(normalized);
|
|
33
|
+
|
|
34
|
+
return isNaN(date.getTime()) ? null : date;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format relative time (e.g., "2d", "3mo", "1y").
|
|
39
|
+
* Compact format suitable for tables and compact UIs.
|
|
40
|
+
*/
|
|
41
|
+
export function formatRelativeTime(dateStr: string | null | undefined): string {
|
|
42
|
+
if (!dateStr) return '-';
|
|
43
|
+
|
|
44
|
+
const date = parseTimestamp(dateStr);
|
|
45
|
+
if (!date) return '-';
|
|
46
|
+
|
|
47
|
+
const now = new Date();
|
|
48
|
+
const diffMs = now.getTime() - date.getTime();
|
|
49
|
+
const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
50
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
51
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
52
|
+
const diffWeeks = Math.floor(diffDays / 7);
|
|
53
|
+
const diffMonths = Math.floor(diffDays / 30);
|
|
54
|
+
const diffYears = Math.floor(diffDays / 365);
|
|
55
|
+
|
|
56
|
+
if (diffMins < 1) return 'now';
|
|
57
|
+
if (diffMins < 60) return `${diffMins}m`;
|
|
58
|
+
if (diffHours < 24) return `${diffHours}h`;
|
|
59
|
+
if (diffDays < 7) return `${diffDays}d`;
|
|
60
|
+
if (diffWeeks < 4) return `${diffWeeks}w`;
|
|
61
|
+
if (diffMonths < 12) return `${diffMonths}mo`;
|
|
62
|
+
return `${diffYears}y`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format full date and time for tooltips and detailed views.
|
|
67
|
+
*/
|
|
68
|
+
export function formatFullDate(dateStr: string | null | undefined): string {
|
|
69
|
+
if (!dateStr) return '';
|
|
70
|
+
|
|
71
|
+
const date = parseTimestamp(dateStr);
|
|
72
|
+
if (!date) return '';
|
|
73
|
+
|
|
74
|
+
return date.toLocaleString('en-US', {
|
|
75
|
+
month: 'short',
|
|
76
|
+
day: 'numeric',
|
|
77
|
+
year: 'numeric',
|
|
78
|
+
hour: '2-digit',
|
|
79
|
+
minute: '2-digit'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Format short date for compact displays (e.g., date range picker).
|
|
85
|
+
*/
|
|
86
|
+
export function formatShortDate(dateStr: string | null | undefined): string {
|
|
87
|
+
if (!dateStr) return '';
|
|
88
|
+
|
|
89
|
+
const date = parseTimestamp(dateStr);
|
|
90
|
+
if (!date) return '';
|
|
91
|
+
|
|
92
|
+
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Format last activity time for agent cards.
|
|
97
|
+
* Shows "Just now", "Xm ago", "Xh ago", or "Xd ago".
|
|
98
|
+
*/
|
|
99
|
+
export function formatLastActivity(timestamp: string | null | undefined): string {
|
|
100
|
+
if (!timestamp) return 'Never';
|
|
101
|
+
|
|
102
|
+
const date = parseTimestamp(timestamp);
|
|
103
|
+
if (!date) return 'Never';
|
|
104
|
+
|
|
105
|
+
const now = new Date();
|
|
106
|
+
const diffMs = now.getTime() - date.getTime();
|
|
107
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
108
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
109
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
110
|
+
|
|
111
|
+
if (diffMins < 1) return 'Just now';
|
|
112
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
113
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
114
|
+
return `${diffDays}d ago`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Format date for display (simple locale string).
|
|
119
|
+
*/
|
|
120
|
+
export function formatDate(dateString: string | null | undefined): string {
|
|
121
|
+
if (!dateString) return 'N/A';
|
|
122
|
+
|
|
123
|
+
const date = parseTimestamp(dateString);
|
|
124
|
+
if (!date) return 'N/A';
|
|
125
|
+
|
|
126
|
+
return date.toLocaleString();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get milliseconds since timestamp (for calculations).
|
|
131
|
+
*/
|
|
132
|
+
export function getTimeSinceMs(timestamp: string | null | undefined): number {
|
|
133
|
+
if (!timestamp) return Infinity;
|
|
134
|
+
|
|
135
|
+
const date = parseTimestamp(timestamp);
|
|
136
|
+
if (!date) return Infinity;
|
|
137
|
+
|
|
138
|
+
return Date.now() - date.getTime();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get minutes since timestamp.
|
|
143
|
+
*/
|
|
144
|
+
export function getTimeSinceMinutes(timestamp: string | null | undefined): number {
|
|
145
|
+
return getTimeSinceMs(timestamp) / 60000;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if timestamp is within a given number of minutes.
|
|
150
|
+
*/
|
|
151
|
+
export function isWithinMinutes(timestamp: string | null | undefined, minutes: number): boolean {
|
|
152
|
+
return getTimeSinceMinutes(timestamp) < minutes;
|
|
153
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mention Parser Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles parsing and rendering of @-mentions in messages.
|
|
5
|
+
* Format: [@Display Name](type:id) -> clickable links
|
|
6
|
+
*
|
|
7
|
+
* The URL generator is configurable — consuming apps provide their own
|
|
8
|
+
* routing via the `urlGenerator` parameter or by setting a default.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface MentionMatch {
|
|
12
|
+
type: string
|
|
13
|
+
id: string
|
|
14
|
+
displayName: string
|
|
15
|
+
url: string
|
|
16
|
+
startIndex: number
|
|
17
|
+
endIndex: number
|
|
18
|
+
fullMatch: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type MentionUrlGenerator = (type: string, id: string) => string
|
|
22
|
+
|
|
23
|
+
// Regex to match mention pattern: [@Display Name](type:id)
|
|
24
|
+
const MENTION_REGEX = /\[@([^\]]+)\]\(([^:]+):([^)]+)\)/g
|
|
25
|
+
|
|
26
|
+
// Default URL generator (apps can override)
|
|
27
|
+
let defaultUrlGenerator: MentionUrlGenerator = (type, id) => `#${type}/${id}`
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set the default URL generator for mentions.
|
|
31
|
+
* Call this once at app startup to configure mention link routing.
|
|
32
|
+
*/
|
|
33
|
+
export function setMentionUrlGenerator(generator: MentionUrlGenerator): void {
|
|
34
|
+
defaultUrlGenerator = generator
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Parse message content and extract all mentions
|
|
39
|
+
*/
|
|
40
|
+
export function parseMentions(
|
|
41
|
+
content: string,
|
|
42
|
+
urlGenerator?: MentionUrlGenerator
|
|
43
|
+
): MentionMatch[] {
|
|
44
|
+
const mentions: MentionMatch[] = []
|
|
45
|
+
const gen = urlGenerator ?? defaultUrlGenerator
|
|
46
|
+
let match
|
|
47
|
+
|
|
48
|
+
MENTION_REGEX.lastIndex = 0
|
|
49
|
+
|
|
50
|
+
while ((match = MENTION_REGEX.exec(content)) !== null) {
|
|
51
|
+
const [fullMatch, displayName, type, id] = match
|
|
52
|
+
|
|
53
|
+
mentions.push({
|
|
54
|
+
type,
|
|
55
|
+
id,
|
|
56
|
+
displayName,
|
|
57
|
+
url: gen(type, id),
|
|
58
|
+
startIndex: match.index,
|
|
59
|
+
endIndex: match.index + fullMatch.length,
|
|
60
|
+
fullMatch
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return mentions
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Convert message content with mentions to HTML
|
|
69
|
+
*/
|
|
70
|
+
export function renderMentionsAsHTML(
|
|
71
|
+
content: string,
|
|
72
|
+
urlGenerator?: MentionUrlGenerator
|
|
73
|
+
): string {
|
|
74
|
+
const mentions = parseMentions(content, urlGenerator)
|
|
75
|
+
|
|
76
|
+
if (mentions.length === 0) {
|
|
77
|
+
return escapeHTML(content)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let result = ''
|
|
81
|
+
let lastIndex = 0
|
|
82
|
+
|
|
83
|
+
mentions.forEach((mention) => {
|
|
84
|
+
result += escapeHTML(content.substring(lastIndex, mention.startIndex))
|
|
85
|
+
result += `<a href="${mention.url}" class="mention-link mention-${mention.type}" data-mention-type="${mention.type}" data-mention-id="${mention.id}">@${escapeHTML(mention.displayName)}</a>`
|
|
86
|
+
lastIndex = mention.endIndex
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
result += escapeHTML(content.substring(lastIndex))
|
|
90
|
+
return result
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create a mention string for insertion into message input
|
|
95
|
+
*/
|
|
96
|
+
export function createMentionString(type: string, id: string, displayName: string): string {
|
|
97
|
+
return `[@${displayName}](${type}:${id})`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Extract plain text from message content (remove mention markup)
|
|
102
|
+
*/
|
|
103
|
+
export function extractPlainText(content: string): string {
|
|
104
|
+
MENTION_REGEX.lastIndex = 0
|
|
105
|
+
return content.replace(MENTION_REGEX, '@$1')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if cursor is inside a mention
|
|
110
|
+
*/
|
|
111
|
+
export function findMentionAtCursor(content: string, cursorPosition: number): MentionMatch | null {
|
|
112
|
+
const mentions = parseMentions(content)
|
|
113
|
+
return (
|
|
114
|
+
mentions.find(
|
|
115
|
+
(mention) => cursorPosition >= mention.startIndex && cursorPosition <= mention.endIndex
|
|
116
|
+
) || null
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Find @ symbol and following text that could be a mention being typed.
|
|
122
|
+
* Supported symbols: @ $ # ! ~ / +
|
|
123
|
+
*/
|
|
124
|
+
export function findPartialMention(
|
|
125
|
+
content: string,
|
|
126
|
+
cursorPosition: number
|
|
127
|
+
): { start: number; query: string } | null {
|
|
128
|
+
const mentionSymbols = ['@', '$', '#', '!', '~', '/', '+']
|
|
129
|
+
let symbolIndex = -1
|
|
130
|
+
|
|
131
|
+
for (let i = cursorPosition - 1; i >= 0; i--) {
|
|
132
|
+
const char = content[i]
|
|
133
|
+
|
|
134
|
+
if (mentionSymbols.includes(char)) {
|
|
135
|
+
symbolIndex = i
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (char === ' ' || char === '\n' || char === ']' || char === ')') {
|
|
140
|
+
break
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (symbolIndex === -1) return null
|
|
145
|
+
|
|
146
|
+
const query = content.substring(symbolIndex + 1, cursorPosition).trim()
|
|
147
|
+
|
|
148
|
+
const existingMention = findMentionAtCursor(content, symbolIndex)
|
|
149
|
+
if (existingMention) return null
|
|
150
|
+
|
|
151
|
+
return { start: symbolIndex, query }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Replace partial mention with complete mention string
|
|
156
|
+
*/
|
|
157
|
+
export function replaceMentionInContent(
|
|
158
|
+
content: string,
|
|
159
|
+
startIndex: number,
|
|
160
|
+
endIndex: number,
|
|
161
|
+
mentionString: string
|
|
162
|
+
): { newContent: string; newCursorPosition: number } {
|
|
163
|
+
const before = content.substring(0, startIndex)
|
|
164
|
+
const after = content.substring(endIndex)
|
|
165
|
+
const newContent = before + mentionString + after
|
|
166
|
+
const newCursorPosition = startIndex + mentionString.length
|
|
167
|
+
|
|
168
|
+
return { newContent, newCursorPosition }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Simple HTML escape function
|
|
173
|
+
*/
|
|
174
|
+
export function escapeHTML(text: string): string {
|
|
175
|
+
return text
|
|
176
|
+
.replace(/&/g, '&')
|
|
177
|
+
.replace(/</g, '<')
|
|
178
|
+
.replace(/>/g, '>')
|
|
179
|
+
.replace(/"/g, '"')
|
|
180
|
+
.replace(/'/g, ''')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Count mentions in content
|
|
185
|
+
*/
|
|
186
|
+
export function countMentions(content: string): number {
|
|
187
|
+
return parseMentions(content).length
|
|
188
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phone number formatting utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent phone number formatting as (xxx) xxx-xxxx
|
|
5
|
+
* Limits input to 10 digits and handles formatting as user types
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Strips all non-numeric characters from a phone number
|
|
10
|
+
*/
|
|
11
|
+
export function stripPhoneNumber(value: string): string {
|
|
12
|
+
return value.replace(/\D/g, '');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Formats a phone number to (xxx) xxx-xxxx format
|
|
17
|
+
*/
|
|
18
|
+
export function formatPhoneNumber(value: string): string {
|
|
19
|
+
const digits = stripPhoneNumber(value);
|
|
20
|
+
const limitedDigits = digits.slice(0, 10);
|
|
21
|
+
|
|
22
|
+
if (limitedDigits.length === 0) {
|
|
23
|
+
return '';
|
|
24
|
+
} else if (limitedDigits.length <= 3) {
|
|
25
|
+
return `(${limitedDigits}`;
|
|
26
|
+
} else if (limitedDigits.length <= 6) {
|
|
27
|
+
return `(${limitedDigits.slice(0, 3)}) ${limitedDigits.slice(3)}`;
|
|
28
|
+
} else {
|
|
29
|
+
return `(${limitedDigits.slice(0, 3)}) ${limitedDigits.slice(3, 6)}-${limitedDigits.slice(6)}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Checks if a phone number has exactly 10 digits
|
|
35
|
+
*/
|
|
36
|
+
export function isCompletePhoneNumber(value: string): boolean {
|
|
37
|
+
return stripPhoneNumber(value).length === 10;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validates phone number format (exactly 10 digits)
|
|
42
|
+
*/
|
|
43
|
+
export function isValidPhoneNumber(value: string): boolean {
|
|
44
|
+
return isCompletePhoneNumber(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handles input formatting with cursor position preservation
|
|
49
|
+
*/
|
|
50
|
+
export function updatePhoneInput(input: HTMLInputElement, newValue: string, oldValue: string): void {
|
|
51
|
+
const cursorPosition = input.selectionStart || 0;
|
|
52
|
+
const oldLength = oldValue.length;
|
|
53
|
+
const newLength = newValue.length;
|
|
54
|
+
|
|
55
|
+
input.value = newValue;
|
|
56
|
+
|
|
57
|
+
let newCursorPosition = cursorPosition;
|
|
58
|
+
|
|
59
|
+
if (newLength > oldLength) {
|
|
60
|
+
newCursorPosition = cursorPosition + (newLength - oldLength);
|
|
61
|
+
} else if (newLength < oldLength) {
|
|
62
|
+
newCursorPosition = Math.max(0, cursorPosition - (oldLength - newLength));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (newCursorPosition === 1 && newValue.startsWith('(')) {
|
|
66
|
+
newCursorPosition = 1;
|
|
67
|
+
} else if (newCursorPosition === 4 && newValue.includes(') ')) {
|
|
68
|
+
newCursorPosition = 6;
|
|
69
|
+
} else if (newCursorPosition === 9 && newValue.includes('-')) {
|
|
70
|
+
newCursorPosition = 10;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
input.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
74
|
+
}
|