@vived/core 2.0.1 → 2.0.2
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 +275 -65
- package/dist/cjs/ExampleFeature/index.js +23 -0
- package/dist/cjs/ExampleFeature/index.js.map +1 -0
- package/dist/esm/ExampleFeature/index.js +7 -0
- package/dist/esm/ExampleFeature/index.js.map +1 -0
- package/dist/types/ExampleFeature/index.d.ts +5 -0
- package/dist/types/ExampleFeature/index.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/AppObject/README.md +476 -0
- package/src/DomainFactories/README.md +154 -0
- package/src/Entities/README.md +340 -0
- package/src/ExampleFeature/README.md +804 -0
- package/src/Types/README.md +549 -0
- package/src/Utilities/README.md +478 -0
- package/src/ValueObjects/README.md +552 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# Utilities
|
|
2
|
+
|
|
3
|
+
The Utilities folder provides a collection of general-purpose helper functions for common programming tasks. These utilities cover color manipulation, animations, unit conversions, file operations, and mathematical transformations.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Utilities are standalone functions that:
|
|
8
|
+
- **Have no dependencies** on other application features
|
|
9
|
+
- **Solve specific problems** with a focused API
|
|
10
|
+
- **Are highly reusable** across different contexts
|
|
11
|
+
- **Include proper validation** with warnings for invalid inputs
|
|
12
|
+
|
|
13
|
+
## Animation & Interpolation
|
|
14
|
+
|
|
15
|
+
### LerpNumber
|
|
16
|
+
|
|
17
|
+
Smooth animation class for interpolating numeric values over time using `requestAnimationFrame`.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { LerpNumber, quintInOut } from "@vived/core";
|
|
21
|
+
|
|
22
|
+
const lerper = new LerpNumber();
|
|
23
|
+
|
|
24
|
+
// Set defaults (optional)
|
|
25
|
+
lerper.defaultDurationMS = 1000;
|
|
26
|
+
lerper.defaultEase = quintInOut;
|
|
27
|
+
|
|
28
|
+
// Animate from 0 to 100 over 2 seconds
|
|
29
|
+
await lerper.lerp({
|
|
30
|
+
start: 0,
|
|
31
|
+
end: 100,
|
|
32
|
+
durationMS: 2000,
|
|
33
|
+
update: (value) => {
|
|
34
|
+
console.log(`Current value: ${value}`);
|
|
35
|
+
},
|
|
36
|
+
onComplete: () => {
|
|
37
|
+
console.log("Animation complete!");
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// With custom easing
|
|
42
|
+
await lerper.lerp({
|
|
43
|
+
start: 0,
|
|
44
|
+
end: 100,
|
|
45
|
+
ease: quadInOut,
|
|
46
|
+
update: (value) => updateUI(value)
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Cancel animation
|
|
50
|
+
if (lerper.isLerping)
|
|
51
|
+
{
|
|
52
|
+
lerper.cancel();
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Configuration Options:**
|
|
57
|
+
- `start` - Starting value
|
|
58
|
+
- `end` - Target value
|
|
59
|
+
- `update` - Callback receiving interpolated values
|
|
60
|
+
- `durationMS` - Animation duration (default: 1000ms)
|
|
61
|
+
- `ease` - Easing function (default: quintInOut)
|
|
62
|
+
- `onComplete` - Called when animation completes naturally
|
|
63
|
+
- `onCancel` - Called when animation is cancelled
|
|
64
|
+
|
|
65
|
+
### Easing Functions
|
|
66
|
+
|
|
67
|
+
Comprehensive collection of easing functions for smooth animations. All functions accept input from 0-1 and return output from 0-1, with automatic clamping.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import {
|
|
71
|
+
easeLinear,
|
|
72
|
+
quadIn, quadOut, quadInOut,
|
|
73
|
+
cubicIn, cubicOut, cubicInOut,
|
|
74
|
+
quartIn, quartOut, quartInOut,
|
|
75
|
+
quintIn, quintOut, quintInOut,
|
|
76
|
+
sinIn, sinOut, sinInOut,
|
|
77
|
+
expoIn, expoOut, expoInOut,
|
|
78
|
+
circIn, circOut, circInOut
|
|
79
|
+
} from "@vived/core";
|
|
80
|
+
|
|
81
|
+
// Linear (constant speed)
|
|
82
|
+
const linear = easeLinear(0.5); // 0.5
|
|
83
|
+
|
|
84
|
+
// Quadratic (x²)
|
|
85
|
+
const easeIn = quadIn(0.5); // 0.25
|
|
86
|
+
const easeOut = quadOut(0.5); // 0.75
|
|
87
|
+
const easeInOut = quadInOut(0.5); // 0.5
|
|
88
|
+
|
|
89
|
+
// Cubic (x³)
|
|
90
|
+
const cubic = cubicInOut(0.5);
|
|
91
|
+
|
|
92
|
+
// Quartic (x⁴)
|
|
93
|
+
const quart = quartInOut(0.5);
|
|
94
|
+
|
|
95
|
+
// Quintic (x⁵)
|
|
96
|
+
const quint = quintInOut(0.5);
|
|
97
|
+
|
|
98
|
+
// Sine wave
|
|
99
|
+
const sine = sinInOut(0.5);
|
|
100
|
+
|
|
101
|
+
// Exponential
|
|
102
|
+
const expo = expoInOut(0.5);
|
|
103
|
+
|
|
104
|
+
// Circular
|
|
105
|
+
const circ = circInOut(0.5);
|
|
106
|
+
|
|
107
|
+
// Use with animations
|
|
108
|
+
const progress = 0.5; // 50% through animation
|
|
109
|
+
const easedProgress = quintInOut(progress);
|
|
110
|
+
const currentValue = start + (end - start) * easedProgress;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Easing Types:**
|
|
114
|
+
- **In** - Slow start, accelerating
|
|
115
|
+
- **Out** - Fast start, decelerating
|
|
116
|
+
- **InOut** - Slow start and end, fast middle
|
|
117
|
+
|
|
118
|
+
**Available Easings:**
|
|
119
|
+
- `easeLinear` - No easing
|
|
120
|
+
- `quad*` - Quadratic (power of 2)
|
|
121
|
+
- `cubic*` - Cubic (power of 3)
|
|
122
|
+
- `quart*` - Quartic (power of 4)
|
|
123
|
+
- `quint*` - Quintic (power of 5)
|
|
124
|
+
- `sin*` - Sine wave
|
|
125
|
+
- `expo*` - Exponential
|
|
126
|
+
- `circ*` - Circular
|
|
127
|
+
|
|
128
|
+
### interpolateNumber
|
|
129
|
+
|
|
130
|
+
Basic numeric interpolation with optional clamping.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { interpolateNumber } from "@vived/core";
|
|
134
|
+
|
|
135
|
+
// Basic interpolation
|
|
136
|
+
const value = interpolateNumber(0, 100, 0.5); // 50
|
|
137
|
+
|
|
138
|
+
// Allow values outside range
|
|
139
|
+
const unclamped = interpolateNumber(0, 100, 1.5); // 150
|
|
140
|
+
|
|
141
|
+
// Clamp to 0-1 range
|
|
142
|
+
const clamped = interpolateNumber(0, 100, 1.5, true); // 100
|
|
143
|
+
|
|
144
|
+
// Negative percent
|
|
145
|
+
const negative = interpolateNumber(0, 100, -0.5); // -50
|
|
146
|
+
const clampedNeg = interpolateNumber(0, 100, -0.5, true); // 0
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Color Utilities
|
|
150
|
+
|
|
151
|
+
### addAlphaToHex
|
|
152
|
+
|
|
153
|
+
Add an alpha channel to a hex color code.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { addAlphaToHex } from "@vived/core";
|
|
157
|
+
|
|
158
|
+
// Full hex with alpha
|
|
159
|
+
const color1 = addAlphaToHex("#ffffff", 0.5); // "#ffffff80"
|
|
160
|
+
const color2 = addAlphaToHex("#ff0000", 1); // "#ff0000ff"
|
|
161
|
+
const color3 = addAlphaToHex("#000000", 0); // "#00000000"
|
|
162
|
+
|
|
163
|
+
// Short hex (automatically expanded)
|
|
164
|
+
const color4 = addAlphaToHex("#fff", 0.5); // "#ffffff80"
|
|
165
|
+
const color5 = addAlphaToHex("#f00", 0.25); // "#ff000040"
|
|
166
|
+
|
|
167
|
+
// Invalid hex returns "#000" with warning
|
|
168
|
+
const invalid = addAlphaToHex("#gggggg", 0.5); // "#000"
|
|
169
|
+
const invalid2 = addAlphaToHex("ffffff", 0.5); // "#000" (missing #)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Alpha Values:**
|
|
173
|
+
- `0` - Fully transparent
|
|
174
|
+
- `0.5` - Semi-transparent
|
|
175
|
+
- `1` - Fully opaque
|
|
176
|
+
|
|
177
|
+
### alphaToHex
|
|
178
|
+
|
|
179
|
+
Convert alpha value (0-1) to hex string.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { alphaToHex } from "@vived/core";
|
|
183
|
+
|
|
184
|
+
const hex1 = alphaToHex(1); // "ff"
|
|
185
|
+
const hex2 = alphaToHex(0.5); // "80"
|
|
186
|
+
const hex3 = alphaToHex(0); // "00"
|
|
187
|
+
const hex4 = alphaToHex(0.25); // "40"
|
|
188
|
+
|
|
189
|
+
// Out of range returns "00" with warning
|
|
190
|
+
const invalid = alphaToHex(1.5); // "00"
|
|
191
|
+
const invalid2 = alphaToHex(-0.1); // "00"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Mathematical Conversions
|
|
195
|
+
|
|
196
|
+
### degreesToRadians
|
|
197
|
+
|
|
198
|
+
Convert degrees to radians.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { degreesToRadians } from "@vived/core";
|
|
202
|
+
|
|
203
|
+
const rad1 = degreesToRadians(180); // ~3.14159 (π)
|
|
204
|
+
const rad2 = degreesToRadians(90); // ~1.5708 (π/2)
|
|
205
|
+
const rad3 = degreesToRadians(45); // ~0.7854 (π/4)
|
|
206
|
+
const rad4 = degreesToRadians(0); // 0
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Length Converters
|
|
210
|
+
|
|
211
|
+
Convert between metric and imperial units.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import {
|
|
215
|
+
inchesToMeters,
|
|
216
|
+
metersToInches,
|
|
217
|
+
feetToMeters,
|
|
218
|
+
metersToFeet
|
|
219
|
+
} from "@vived/core";
|
|
220
|
+
|
|
221
|
+
// Inches ↔ Meters
|
|
222
|
+
const meters1 = inchesToMeters(39.37); // ~1
|
|
223
|
+
const inches = metersToInches(1); // ~39.37
|
|
224
|
+
|
|
225
|
+
// Feet ↔ Meters
|
|
226
|
+
const meters2 = feetToMeters(3.28); // ~1
|
|
227
|
+
const feet = metersToFeet(1); // ~3.28
|
|
228
|
+
|
|
229
|
+
// Practical examples
|
|
230
|
+
const screenWidth = 27; // inches
|
|
231
|
+
const screenWidthM = inchesToMeters(screenWidth); // ~0.686 meters
|
|
232
|
+
|
|
233
|
+
const roomLength = 5; // meters
|
|
234
|
+
const roomLengthFt = metersToFeet(roomLength); // ~16.4 feet
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Conversion Factors:**
|
|
238
|
+
- 1 meter = 39.3701 inches
|
|
239
|
+
- 1 meter = 3.28084 feet
|
|
240
|
+
|
|
241
|
+
## File Operations
|
|
242
|
+
|
|
243
|
+
### downloadFile
|
|
244
|
+
|
|
245
|
+
Trigger a browser download for a Blob.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { downloadFile } from "@vived/core";
|
|
249
|
+
|
|
250
|
+
// Download text file
|
|
251
|
+
const textBlob = new Blob(["Hello World"], { type: "text/plain" });
|
|
252
|
+
downloadFile("hello.txt", textBlob);
|
|
253
|
+
|
|
254
|
+
// Download JSON
|
|
255
|
+
const data = { name: "John", age: 30 };
|
|
256
|
+
const jsonBlob = new Blob([JSON.stringify(data)], { type: "application/json" });
|
|
257
|
+
downloadFile("data.json", jsonBlob);
|
|
258
|
+
|
|
259
|
+
// Download image
|
|
260
|
+
const canvas = document.getElementById("myCanvas") as HTMLCanvasElement;
|
|
261
|
+
canvas.toBlob((blob) => {
|
|
262
|
+
if (blob)
|
|
263
|
+
{
|
|
264
|
+
downloadFile("image.png", blob);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Download CSV
|
|
269
|
+
const csvContent = "Name,Age\nJohn,30\nJane,25";
|
|
270
|
+
const csvBlob = new Blob([csvContent], { type: "text/csv" });
|
|
271
|
+
downloadFile("data.csv", csvBlob);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**How it works:**
|
|
275
|
+
1. Creates a temporary anchor element
|
|
276
|
+
2. Creates an object URL from the Blob
|
|
277
|
+
3. Triggers a download
|
|
278
|
+
4. Cleans up the anchor and URL
|
|
279
|
+
|
|
280
|
+
## ID Generation
|
|
281
|
+
|
|
282
|
+
### generateUniqueID
|
|
283
|
+
|
|
284
|
+
Generate a universally unique identifier (UUID v4).
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { generateUniqueID } from "@vived/core";
|
|
288
|
+
|
|
289
|
+
const id1 = generateUniqueID(); // "a3bb189e-8bf9-3888-9912-ace4e6543002"
|
|
290
|
+
const id2 = generateUniqueID(); // "f8e7d5c6-9a4b-4c5d-8e6f-7a8b9c0d1e2f"
|
|
291
|
+
|
|
292
|
+
// Use for entity IDs
|
|
293
|
+
const userId = generateUniqueID();
|
|
294
|
+
const sessionId = generateUniqueID();
|
|
295
|
+
|
|
296
|
+
// Use for tracking
|
|
297
|
+
const eventId = generateUniqueID();
|
|
298
|
+
const transactionId = generateUniqueID();
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Properties:**
|
|
302
|
+
- RFC 4122 compliant UUID v4
|
|
303
|
+
- 122 bits of randomness
|
|
304
|
+
- Extremely low collision probability
|
|
305
|
+
- Safe for distributed systems
|
|
306
|
+
|
|
307
|
+
## Usage Patterns
|
|
308
|
+
|
|
309
|
+
### Smooth Property Animation
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
import { LerpNumber, quintInOut } from "@vived/core";
|
|
313
|
+
|
|
314
|
+
class AnimatedObject
|
|
315
|
+
{
|
|
316
|
+
private lerper = new LerpNumber();
|
|
317
|
+
private _opacity = 1;
|
|
318
|
+
|
|
319
|
+
async fadeOut()
|
|
320
|
+
{
|
|
321
|
+
await this.lerper.lerp({
|
|
322
|
+
start: this._opacity,
|
|
323
|
+
end: 0,
|
|
324
|
+
durationMS: 500,
|
|
325
|
+
ease: quintInOut,
|
|
326
|
+
update: (value) => {
|
|
327
|
+
this._opacity = value;
|
|
328
|
+
this.render();
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async fadeIn()
|
|
334
|
+
{
|
|
335
|
+
await this.lerper.lerp({
|
|
336
|
+
start: this._opacity,
|
|
337
|
+
end: 1,
|
|
338
|
+
durationMS: 500,
|
|
339
|
+
ease: quintInOut,
|
|
340
|
+
update: (value) => {
|
|
341
|
+
this._opacity = value;
|
|
342
|
+
this.render();
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
render()
|
|
348
|
+
{
|
|
349
|
+
// Update visual representation
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Color with Transparency
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { addAlphaToHex } from "@vived/core";
|
|
358
|
+
|
|
359
|
+
function createOverlay(baseColor: string, opacity: number): string
|
|
360
|
+
{
|
|
361
|
+
return addAlphaToHex(baseColor, opacity);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const overlay = createOverlay("#000000", 0.7); // Semi-transparent black
|
|
365
|
+
document.body.style.backgroundColor = overlay;
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Unit Conversion in VR
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
import { feetToMeters, metersToFeet } from "@vived/core";
|
|
372
|
+
|
|
373
|
+
class VRRoom
|
|
374
|
+
{
|
|
375
|
+
// User inputs in feet
|
|
376
|
+
setDimensions(widthFeet: number, heightFeet: number, depthFeet: number)
|
|
377
|
+
{
|
|
378
|
+
// Convert to meters for internal use
|
|
379
|
+
this.width = feetToMeters(widthFeet);
|
|
380
|
+
this.height = feetToMeters(heightFeet);
|
|
381
|
+
this.depth = feetToMeters(depthFeet);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Display in feet for user
|
|
385
|
+
getDimensionsDisplay(): string
|
|
386
|
+
{
|
|
387
|
+
const w = metersToFeet(this.width).toFixed(1);
|
|
388
|
+
const h = metersToFeet(this.height).toFixed(1);
|
|
389
|
+
const d = metersToFeet(this.depth).toFixed(1);
|
|
390
|
+
return `${w}' × ${h}' × ${d}'`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private width = 0;
|
|
394
|
+
private height = 0;
|
|
395
|
+
private depth = 0;
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Export Application Data
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { downloadFile, generateUniqueID } from "@vived/core";
|
|
403
|
+
|
|
404
|
+
function exportUserData(userData: any)
|
|
405
|
+
{
|
|
406
|
+
const exportData = {
|
|
407
|
+
id: generateUniqueID(),
|
|
408
|
+
timestamp: new Date().toISOString(),
|
|
409
|
+
data: userData
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const json = JSON.stringify(exportData, null, 2);
|
|
413
|
+
const blob = new Blob([json], { type: "application/json" });
|
|
414
|
+
downloadFile(`export-${Date.now()}.json`, blob);
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Custom Easing with Interpolation
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { interpolateNumber, expoOut } from "@vived/core";
|
|
422
|
+
|
|
423
|
+
function animateWithEasing(
|
|
424
|
+
start: number,
|
|
425
|
+
end: number,
|
|
426
|
+
progress: number
|
|
427
|
+
): number
|
|
428
|
+
{
|
|
429
|
+
const easedProgress = expoOut(progress);
|
|
430
|
+
return interpolateNumber(start, end, easedProgress);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Use in animation loop
|
|
434
|
+
let progress = 0;
|
|
435
|
+
const interval = setInterval(() => {
|
|
436
|
+
progress += 0.01;
|
|
437
|
+
const value = animateWithEasing(0, 100, progress);
|
|
438
|
+
updateUI(value);
|
|
439
|
+
|
|
440
|
+
if (progress >= 1)
|
|
441
|
+
{
|
|
442
|
+
clearInterval(interval);
|
|
443
|
+
}
|
|
444
|
+
}, 16); // ~60fps
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Validation & Error Handling
|
|
448
|
+
|
|
449
|
+
Most utilities include validation with console warnings:
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// Invalid hex returns fallback with warning
|
|
453
|
+
addAlphaToHex("invalid", 0.5); // "#000" + console.warn()
|
|
454
|
+
|
|
455
|
+
// Out of range returns fallback with warning
|
|
456
|
+
alphaToHex(1.5); // "00" + console.warn()
|
|
457
|
+
|
|
458
|
+
// Interpolation with clamping prevents out-of-range
|
|
459
|
+
interpolateNumber(0, 100, 1.5, true); // 100 (clamped)
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Performance Considerations
|
|
463
|
+
|
|
464
|
+
- **LerpNumber** - Uses `requestAnimationFrame` for smooth animations aligned with browser repaints
|
|
465
|
+
- **Easing functions** - Optimized mathematical operations with early returns for edge cases
|
|
466
|
+
- **UUID generation** - Uses native crypto APIs for secure random numbers
|
|
467
|
+
- **Interpolation** - Simple arithmetic operations with minimal overhead
|
|
468
|
+
- **Color conversion** - String operations are lightweight but use sparingly in tight loops
|
|
469
|
+
|
|
470
|
+
## Use Cases
|
|
471
|
+
|
|
472
|
+
- **UI Animations** - Smooth transitions for opacity, position, scale
|
|
473
|
+
- **Color Manipulation** - Dynamic theming with transparency
|
|
474
|
+
- **Data Export** - Download user-generated content or reports
|
|
475
|
+
- **Unit Conversion** - VR/AR applications with real-world measurements
|
|
476
|
+
- **Game Development** - Custom easing curves for game feel
|
|
477
|
+
- **Entity Management** - Unique IDs for tracking objects
|
|
478
|
+
- **Chart Animations** - Smooth value changes in data visualizations
|