@product7/product7-js 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/README.md +1025 -0
- package/dist/README.md +1025 -0
- package/dist/product7-js.js +14658 -0
- package/dist/product7-js.js.map +1 -0
- package/dist/product7-js.min.js +2 -0
- package/dist/product7-js.min.js.map +1 -0
- package/package.json +114 -0
- package/src/api/mock-data/index.js +360 -0
- package/src/api/services/ChangelogService.js +28 -0
- package/src/api/services/FeedbackService.js +44 -0
- package/src/api/services/HelpService.js +50 -0
- package/src/api/services/MessengerService.js +279 -0
- package/src/api/services/SurveyService.js +127 -0
- package/src/api/utils/helpers.js +30 -0
- package/src/core/APIService.js +303 -0
- package/src/core/BaseAPIService.js +298 -0
- package/src/core/EventBus.js +54 -0
- package/src/core/Product7.js +812 -0
- package/src/core/WebSocketService.js +275 -0
- package/src/docs/api.md +226 -0
- package/src/docs/example.md +461 -0
- package/src/docs/framework-integrations.md +714 -0
- package/src/docs/installation.md +281 -0
- package/src/index.js +894 -0
- package/src/styles/base.js +50 -0
- package/src/styles/changelog.js +665 -0
- package/src/styles/components.js +553 -0
- package/src/styles/design-tokens.js +124 -0
- package/src/styles/feedback.js +325 -0
- package/src/styles/messenger-components.js +632 -0
- package/src/styles/messenger-core.js +233 -0
- package/src/styles/messenger-features.js +169 -0
- package/src/styles/messenger-views.js +877 -0
- package/src/styles/messenger.js +17 -0
- package/src/styles/messengerCustomStyles.js +114 -0
- package/src/styles/styles.js +26 -0
- package/src/styles/survey.js +894 -0
- package/src/utils/errors.js +142 -0
- package/src/utils/helpers.js +219 -0
- package/src/widgets/BaseWidget.js +548 -0
- package/src/widgets/ButtonWidget.js +104 -0
- package/src/widgets/ChangelogWidget.js +615 -0
- package/src/widgets/InlineWidget.js +148 -0
- package/src/widgets/MessengerWidget.js +979 -0
- package/src/widgets/SurveyWidget.js +1325 -0
- package/src/widgets/TabWidget.js +45 -0
- package/src/widgets/WidgetFactory.js +70 -0
- package/src/widgets/messenger/MessengerState.js +323 -0
- package/src/widgets/messenger/components/MessengerLauncher.js +124 -0
- package/src/widgets/messenger/components/MessengerPanel.js +111 -0
- package/src/widgets/messenger/components/NavigationTabs.js +130 -0
- package/src/widgets/messenger/views/ChangelogView.js +167 -0
- package/src/widgets/messenger/views/ChatView.js +592 -0
- package/src/widgets/messenger/views/ConversationsView.js +244 -0
- package/src/widgets/messenger/views/HelpView.js +239 -0
- package/src/widgets/messenger/views/HomeView.js +300 -0
- package/src/widgets/messenger/views/PreChatFormView.js +109 -0
- package/types/index.d.ts +341 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocketService - Real-time communication for messenger widget
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class WebSocketService {
|
|
6
|
+
constructor(config = {}) {
|
|
7
|
+
this.baseURL = config.baseURL || '';
|
|
8
|
+
this.workspace = config.workspace || '';
|
|
9
|
+
this.sessionToken = config.sessionToken || null;
|
|
10
|
+
this.mock = config.mock || false;
|
|
11
|
+
|
|
12
|
+
this.ws = null;
|
|
13
|
+
this.reconnectAttempts = 0;
|
|
14
|
+
this.maxReconnectAttempts = 5;
|
|
15
|
+
this.reconnectDelay = 1000;
|
|
16
|
+
this.pingInterval = null;
|
|
17
|
+
this.isConnected = false;
|
|
18
|
+
|
|
19
|
+
// Event listeners
|
|
20
|
+
this._listeners = new Map();
|
|
21
|
+
|
|
22
|
+
// Bind methods
|
|
23
|
+
this._onOpen = this._onOpen.bind(this);
|
|
24
|
+
this._onMessage = this._onMessage.bind(this);
|
|
25
|
+
this._onClose = this._onClose.bind(this);
|
|
26
|
+
this._onError = this._onError.bind(this);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Connect to WebSocket server
|
|
31
|
+
*/
|
|
32
|
+
connect(sessionToken = null) {
|
|
33
|
+
if (sessionToken) {
|
|
34
|
+
this.sessionToken = sessionToken;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!this.sessionToken) {
|
|
38
|
+
console.warn('[WebSocket] No session token provided');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Mock mode - simulate connection
|
|
43
|
+
if (this.mock) {
|
|
44
|
+
this.isConnected = true;
|
|
45
|
+
this._emit('connected', {});
|
|
46
|
+
this._startMockResponses();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Build WebSocket URL
|
|
51
|
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
52
|
+
let wsURL = this.baseURL.replace(/^https?:/, wsProtocol);
|
|
53
|
+
wsURL = wsURL.replace('/api/v1', '');
|
|
54
|
+
wsURL = `${wsURL}/api/v1/widget/messenger/ws?token=${encodeURIComponent(this.sessionToken)}`;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
this.ws = new WebSocket(wsURL);
|
|
58
|
+
this.ws.onopen = this._onOpen;
|
|
59
|
+
this.ws.onmessage = this._onMessage;
|
|
60
|
+
this.ws.onclose = this._onClose;
|
|
61
|
+
this.ws.onerror = this._onError;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('[WebSocket] Connection error:', error);
|
|
64
|
+
this._scheduleReconnect();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Disconnect from WebSocket server
|
|
70
|
+
*/
|
|
71
|
+
disconnect() {
|
|
72
|
+
this.isConnected = false;
|
|
73
|
+
this.reconnectAttempts = this.maxReconnectAttempts; // Prevent reconnection
|
|
74
|
+
|
|
75
|
+
if (this.pingInterval) {
|
|
76
|
+
clearInterval(this.pingInterval);
|
|
77
|
+
this.pingInterval = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (this.ws) {
|
|
81
|
+
this.ws.close();
|
|
82
|
+
this.ws = null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (this._mockInterval) {
|
|
86
|
+
clearInterval(this._mockInterval);
|
|
87
|
+
this._mockInterval = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Subscribe to events
|
|
93
|
+
* @param {string} event - Event name
|
|
94
|
+
* @param {Function} callback - Event handler
|
|
95
|
+
* @returns {Function} Unsubscribe function
|
|
96
|
+
*/
|
|
97
|
+
on(event, callback) {
|
|
98
|
+
if (!this._listeners.has(event)) {
|
|
99
|
+
this._listeners.set(event, new Set());
|
|
100
|
+
}
|
|
101
|
+
this._listeners.get(event).add(callback);
|
|
102
|
+
return () => this._listeners.get(event).delete(callback);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Remove event listener
|
|
107
|
+
*/
|
|
108
|
+
off(event, callback) {
|
|
109
|
+
if (this._listeners.has(event)) {
|
|
110
|
+
this._listeners.get(event).delete(callback);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Send message through WebSocket
|
|
116
|
+
*/
|
|
117
|
+
send(type, payload = {}) {
|
|
118
|
+
if (!this.isConnected) {
|
|
119
|
+
console.warn('[WebSocket] Not connected, cannot send message');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (this.mock) {
|
|
124
|
+
// Mock mode - just log
|
|
125
|
+
console.log('[WebSocket Mock] Sending:', type, payload);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
130
|
+
this.ws.send(JSON.stringify({ type, payload }));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Private methods
|
|
135
|
+
|
|
136
|
+
_onOpen() {
|
|
137
|
+
console.log('[WebSocket] Connected');
|
|
138
|
+
this.isConnected = true;
|
|
139
|
+
this.reconnectAttempts = 0;
|
|
140
|
+
this._emit('connected', {});
|
|
141
|
+
|
|
142
|
+
// Start ping interval to keep connection alive
|
|
143
|
+
this.pingInterval = setInterval(() => {
|
|
144
|
+
this.send('ping', {});
|
|
145
|
+
}, 30000);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
_onMessage(event) {
|
|
149
|
+
try {
|
|
150
|
+
const data = JSON.parse(event.data);
|
|
151
|
+
const { type, payload } = data;
|
|
152
|
+
|
|
153
|
+
// Handle different event types
|
|
154
|
+
switch (type) {
|
|
155
|
+
case 'message:new':
|
|
156
|
+
this._emit('message', payload);
|
|
157
|
+
break;
|
|
158
|
+
case 'typing:started':
|
|
159
|
+
this._emit('typing_started', payload);
|
|
160
|
+
break;
|
|
161
|
+
case 'typing:stopped':
|
|
162
|
+
this._emit('typing_stopped', payload);
|
|
163
|
+
break;
|
|
164
|
+
case 'conversation:updated':
|
|
165
|
+
this._emit('conversation_updated', payload);
|
|
166
|
+
break;
|
|
167
|
+
case 'conversation:closed':
|
|
168
|
+
this._emit('conversation_closed', payload);
|
|
169
|
+
break;
|
|
170
|
+
case 'availability:changed':
|
|
171
|
+
this._emit('availability_changed', payload);
|
|
172
|
+
break;
|
|
173
|
+
case 'pong':
|
|
174
|
+
// Ping response, ignore
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
console.log('[WebSocket] Unknown event:', type, payload);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error('[WebSocket] Failed to parse message:', error);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_onClose(event) {
|
|
185
|
+
console.log('[WebSocket] Disconnected:', event.code, event.reason);
|
|
186
|
+
this.isConnected = false;
|
|
187
|
+
|
|
188
|
+
if (this.pingInterval) {
|
|
189
|
+
clearInterval(this.pingInterval);
|
|
190
|
+
this.pingInterval = null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this._emit('disconnected', { code: event.code, reason: event.reason });
|
|
194
|
+
this._scheduleReconnect();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_onError(error) {
|
|
198
|
+
console.error('[WebSocket] Error:', error);
|
|
199
|
+
this._emit('error', { error });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_scheduleReconnect() {
|
|
203
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
204
|
+
console.log('[WebSocket] Max reconnect attempts reached');
|
|
205
|
+
this._emit('reconnect_failed', {});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.reconnectAttempts++;
|
|
210
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
211
|
+
console.log(
|
|
212
|
+
`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
setTimeout(() => {
|
|
216
|
+
this.connect();
|
|
217
|
+
}, delay);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
_emit(event, data) {
|
|
221
|
+
if (this._listeners.has(event)) {
|
|
222
|
+
this._listeners.get(event).forEach((callback) => {
|
|
223
|
+
try {
|
|
224
|
+
callback(data);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error(`[WebSocket] Error in ${event} handler:`, error);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Mock support for development
|
|
233
|
+
_startMockResponses() {
|
|
234
|
+
// Simulate agent typing and responses
|
|
235
|
+
this._mockInterval = setInterval(() => {
|
|
236
|
+
// Randomly emit typing or message events for demo
|
|
237
|
+
const random = Math.random();
|
|
238
|
+
if (random < 0.1) {
|
|
239
|
+
this._emit('typing_started', {
|
|
240
|
+
conversation_id: 'conv_1',
|
|
241
|
+
user_id: 'agent_1',
|
|
242
|
+
user_name: 'Sarah',
|
|
243
|
+
is_agent: true,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Stop typing after 2 seconds
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
this._emit('typing_stopped', {
|
|
249
|
+
conversation_id: 'conv_1',
|
|
250
|
+
user_id: 'agent_1',
|
|
251
|
+
});
|
|
252
|
+
}, 2000);
|
|
253
|
+
}
|
|
254
|
+
}, 10000);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Simulate receiving a message (for mock mode)
|
|
259
|
+
*/
|
|
260
|
+
simulateMessage(conversationId, message) {
|
|
261
|
+
if (this.mock) {
|
|
262
|
+
this._emit('message', {
|
|
263
|
+
conversation_id: conversationId,
|
|
264
|
+
message: {
|
|
265
|
+
id: 'msg_' + Date.now(),
|
|
266
|
+
content: message.content,
|
|
267
|
+
sender_type: message.sender_type || 'agent',
|
|
268
|
+
sender_name: message.sender_name || 'Support',
|
|
269
|
+
sender_avatar: message.sender_avatar || null,
|
|
270
|
+
created_at: new Date().toISOString(),
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
package/src/docs/api.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## Product7
|
|
4
|
+
|
|
5
|
+
### Constructor
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
new Product7(config);
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Config Options:**
|
|
12
|
+
|
|
13
|
+
| Option | Type | Required | Default | Description |
|
|
14
|
+
| -------------- | ------- | -------- | ---------------- | --------------------- |
|
|
15
|
+
| `workspace` | String | Yes | - | Workspace identifier |
|
|
16
|
+
| `metadata` | Object | Yes | - | User identification |
|
|
17
|
+
| `apiUrl` | String | No | Auto | Custom API endpoint |
|
|
18
|
+
| `boardName` | String | No | `'general'` | Default board |
|
|
19
|
+
| `theme` | String | No | `'light'` | `'light'` or `'dark'` |
|
|
20
|
+
| `position` | String | No | `'bottom-right'` | Widget position |
|
|
21
|
+
| `showBackdrop` | Boolean | No | `true` | Show panel backdrop |
|
|
22
|
+
| `debug` | Boolean | No | `false` | Debug logging |
|
|
23
|
+
|
|
24
|
+
### Methods
|
|
25
|
+
|
|
26
|
+
#### `async init(): Promise<Object>`
|
|
27
|
+
|
|
28
|
+
Initialize SDK and create session.
|
|
29
|
+
|
|
30
|
+
#### `createWidget(type, options?): Widget`
|
|
31
|
+
|
|
32
|
+
Create widget. Type: `'button'`.
|
|
33
|
+
|
|
34
|
+
#### `getWidget(id): Widget | undefined`
|
|
35
|
+
|
|
36
|
+
Get widget by ID.
|
|
37
|
+
|
|
38
|
+
#### `getAllWidgets(): Array<Widget>`
|
|
39
|
+
|
|
40
|
+
Get all widgets.
|
|
41
|
+
|
|
42
|
+
#### `destroyWidget(id): Boolean`
|
|
43
|
+
|
|
44
|
+
Destroy widget by ID.
|
|
45
|
+
|
|
46
|
+
#### `destroyAllWidgets(): void`
|
|
47
|
+
|
|
48
|
+
Destroy all widgets.
|
|
49
|
+
|
|
50
|
+
#### `updateConfig(config): void`
|
|
51
|
+
|
|
52
|
+
Update configuration.
|
|
53
|
+
|
|
54
|
+
#### `setMetadata(metadata): void`
|
|
55
|
+
|
|
56
|
+
Update user context.
|
|
57
|
+
|
|
58
|
+
#### `getMetadata(): Object | null`
|
|
59
|
+
|
|
60
|
+
Get current user context.
|
|
61
|
+
|
|
62
|
+
#### `async reinitialize(metadata?): Promise<Object>`
|
|
63
|
+
|
|
64
|
+
Reinitialize with new user.
|
|
65
|
+
|
|
66
|
+
#### `on(event, callback): Product7`
|
|
67
|
+
|
|
68
|
+
Subscribe to event.
|
|
69
|
+
|
|
70
|
+
#### `off(event, callback): Product7`
|
|
71
|
+
|
|
72
|
+
Unsubscribe from event.
|
|
73
|
+
|
|
74
|
+
#### `once(event, callback): Product7`
|
|
75
|
+
|
|
76
|
+
Subscribe once.
|
|
77
|
+
|
|
78
|
+
#### `destroy(): void`
|
|
79
|
+
|
|
80
|
+
Destroy SDK.
|
|
81
|
+
|
|
82
|
+
### Static Methods
|
|
83
|
+
|
|
84
|
+
#### `Product7.create(config): Product7`
|
|
85
|
+
|
|
86
|
+
Create SDK instance.
|
|
87
|
+
|
|
88
|
+
#### `async Product7.createAndInit(config): Promise<Product7>`
|
|
89
|
+
|
|
90
|
+
Create and initialize.
|
|
91
|
+
|
|
92
|
+
#### `Product7.extractMetadataFromAuth(authData): Object`
|
|
93
|
+
|
|
94
|
+
Extract user context from auth.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Widget
|
|
99
|
+
|
|
100
|
+
### ButtonWidget
|
|
101
|
+
|
|
102
|
+
#### Methods
|
|
103
|
+
|
|
104
|
+
##### `mount(container?): Widget`
|
|
105
|
+
|
|
106
|
+
Mount to DOM.
|
|
107
|
+
|
|
108
|
+
##### `show(): Widget`
|
|
109
|
+
|
|
110
|
+
Show widget.
|
|
111
|
+
|
|
112
|
+
##### `hide(): Widget`
|
|
113
|
+
|
|
114
|
+
Hide widget.
|
|
115
|
+
|
|
116
|
+
##### `openPanel(): void`
|
|
117
|
+
|
|
118
|
+
Open feedback panel.
|
|
119
|
+
|
|
120
|
+
##### `closePanel(): void`
|
|
121
|
+
|
|
122
|
+
Close panel.
|
|
123
|
+
|
|
124
|
+
##### `updateText(text): void`
|
|
125
|
+
|
|
126
|
+
Update button text.
|
|
127
|
+
|
|
128
|
+
##### `updatePosition(position): void`
|
|
129
|
+
|
|
130
|
+
Update position.
|
|
131
|
+
|
|
132
|
+
##### `destroy(): void`
|
|
133
|
+
|
|
134
|
+
Destroy widget.
|
|
135
|
+
|
|
136
|
+
#### Properties
|
|
137
|
+
|
|
138
|
+
- `id: String` - Widget ID
|
|
139
|
+
- `type: String` - Widget type
|
|
140
|
+
- `mounted: Boolean` - Mount status
|
|
141
|
+
- `state: Object` - Widget state
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Events
|
|
146
|
+
|
|
147
|
+
### SDK Events
|
|
148
|
+
|
|
149
|
+
- `sdk:initialized` - SDK ready
|
|
150
|
+
- `sdk:error` - Error occurred
|
|
151
|
+
- `sdk:destroyed` - SDK destroyed
|
|
152
|
+
- `config:updated` - Config changed
|
|
153
|
+
- `user:updated` - User changed
|
|
154
|
+
|
|
155
|
+
### Widget Events
|
|
156
|
+
|
|
157
|
+
- `widget:created` - Widget created
|
|
158
|
+
- `widget:mounted` - Widget mounted
|
|
159
|
+
- `widget:destroyed` - Widget destroyed
|
|
160
|
+
- `widgets:cleared` - All cleared
|
|
161
|
+
|
|
162
|
+
### Feedback Events
|
|
163
|
+
|
|
164
|
+
- `feedback:submitted` - Feedback sent
|
|
165
|
+
- `feedback:error` - Submission failed
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## APIService
|
|
170
|
+
|
|
171
|
+
Access via `sdk.apiService`.
|
|
172
|
+
|
|
173
|
+
### Methods
|
|
174
|
+
|
|
175
|
+
#### `async init(metadata?): Promise<Object>`
|
|
176
|
+
|
|
177
|
+
Initialize session.
|
|
178
|
+
|
|
179
|
+
#### `async submitFeedback(data): Promise<Object>`
|
|
180
|
+
|
|
181
|
+
Submit feedback.
|
|
182
|
+
|
|
183
|
+
#### `isSessionValid(): Boolean`
|
|
184
|
+
|
|
185
|
+
Check session validity.
|
|
186
|
+
|
|
187
|
+
#### `clearSession(): void`
|
|
188
|
+
|
|
189
|
+
Clear session.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Error Classes
|
|
194
|
+
|
|
195
|
+
### SDKError
|
|
196
|
+
|
|
197
|
+
General SDK errors.
|
|
198
|
+
|
|
199
|
+
### APIError
|
|
200
|
+
|
|
201
|
+
API errors with methods:
|
|
202
|
+
|
|
203
|
+
- `isNetworkError()`
|
|
204
|
+
- `isClientError()`
|
|
205
|
+
- `isServerError()`
|
|
206
|
+
|
|
207
|
+
### ConfigError
|
|
208
|
+
|
|
209
|
+
Configuration errors.
|
|
210
|
+
|
|
211
|
+
### WidgetError
|
|
212
|
+
|
|
213
|
+
Widget errors.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Utilities
|
|
218
|
+
|
|
219
|
+
- `generateId(prefix?)`
|
|
220
|
+
- `deepMerge(target, source)`
|
|
221
|
+
- `debounce(func, wait)`
|
|
222
|
+
- `throttle(func, limit)`
|
|
223
|
+
- `isValidEmail(email)`
|
|
224
|
+
- `isMobile()`
|
|
225
|
+
- `formatFileSize(bytes)`
|
|
226
|
+
- `delay(ms)`
|