@synnaxlabs/x 0.32.0 → 0.33.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/.turbo/turbo-build.log +23 -23
- package/dist/binary.cjs +1 -1
- package/dist/binary.js +1 -1
- package/dist/bounds-CCueigU3.js +171 -0
- package/dist/bounds-Cudf5M4H.cjs +1 -0
- package/dist/bounds.cjs +1 -1
- package/dist/bounds.js +1 -1
- package/dist/box-CZVdKCOc.cjs +1 -0
- package/dist/box-hAkmDC3K.js +201 -0
- package/dist/box.cjs +1 -1
- package/dist/box.js +1 -1
- package/dist/caseconv.cjs +1 -1
- package/dist/caseconv.js +1 -1
- package/dist/index-By0n2R_b.cjs +1 -0
- package/dist/{index-DgaYJC35.cjs → index-DQZfhLnw.cjs} +1 -1
- package/dist/{index-Duv1uH08.js → index-q_1Jz5rY.js} +5 -5
- package/dist/{index-B5THJ1eb.js → index-zsix_qnl.js} +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +163 -146
- package/dist/{location-DjcaXEps.js → location-B5rSnQP3.js} +1 -1
- package/dist/{location-gPB1RtfA.cjs → location-YGxhLPDy.cjs} +1 -1
- package/dist/location.cjs +1 -1
- package/dist/location.js +1 -1
- package/dist/{position-DkON65EZ.js → position-BZOTg74V.js} +2 -2
- package/dist/{position-C71OiHiw.cjs → position-CjNCcq8X.cjs} +1 -1
- package/dist/position.cjs +1 -1
- package/dist/position.js +1 -1
- package/dist/{scale-COPgp55a.cjs → scale-BvbW9p2C.cjs} +1 -1
- package/dist/{scale-qw6vRO4s.js → scale-C7_4I3pa.js} +3 -3
- package/dist/scale.cjs +1 -1
- package/dist/scale.js +1 -1
- package/dist/series-BId9slhU.cjs +11 -0
- package/dist/{series-B5eA90Ci.js → series-CZw97Bq2.js} +619 -456
- package/dist/spatial.cjs +1 -1
- package/dist/spatial.js +5 -5
- package/dist/src/caseconv/caseconv.d.ts.map +1 -1
- package/dist/src/deep/path.d.ts +1 -1
- package/dist/src/deep/path.d.ts.map +1 -1
- package/dist/src/math/math.d.ts +26 -6
- package/dist/src/math/math.d.ts.map +1 -1
- package/dist/src/math/math.spec.d.ts +2 -0
- package/dist/src/math/math.spec.d.ts.map +1 -0
- package/dist/src/record.d.ts +4 -0
- package/dist/src/record.d.ts.map +1 -1
- package/dist/src/spatial/bounds/bounds.d.ts +204 -2
- package/dist/src/spatial/bounds/bounds.d.ts.map +1 -1
- package/dist/src/spatial/box/box.d.ts +4 -4
- package/dist/src/spatial/box/box.d.ts.map +1 -1
- package/dist/src/strings/strings.d.ts +14 -0
- package/dist/src/strings/strings.d.ts.map +1 -1
- package/dist/src/telem/series.d.ts +35 -10
- package/dist/src/telem/series.d.ts.map +1 -1
- package/dist/src/telem/telem.d.ts +12 -10
- package/dist/src/telem/telem.d.ts.map +1 -1
- package/dist/telem.cjs +1 -1
- package/dist/telem.js +1 -1
- package/package.json +9 -9
- package/src/caseconv/caseconv.ts +1 -0
- package/src/deep/path.ts +1 -1
- package/src/math/math.spec.ts +149 -0
- package/src/math/math.ts +60 -9
- package/src/record.ts +5 -0
- package/src/spatial/bounds/bounds.spec.ts +135 -270
- package/src/spatial/bounds/bounds.ts +290 -25
- package/src/spatial/box/box.ts +9 -5
- package/src/strings/strings.spec.ts +33 -1
- package/src/strings/strings.ts +52 -0
- package/src/telem/series.spec.ts +235 -0
- package/src/telem/series.ts +271 -52
- package/src/telem/telem.spec.ts +22 -0
- package/src/telem/telem.ts +44 -20
- package/src/zodutil/zodutil.spec.ts +5 -7
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/bounds-CpboA0q6.js +0 -127
- package/dist/bounds-ZZc1c-_Z.cjs +0 -1
- package/dist/box-BQID-0jO.cjs +0 -1
- package/dist/box-xRqO6NvI.js +0 -202
- package/dist/index-xk130iQA.cjs +0 -1
- package/dist/series-CJ65b1Uz.cjs +0 -11
|
@@ -222,14 +222,89 @@ export const linspace = <T extends numeric.Value = number>(bounds: Crude<T>): T[
|
|
|
222
222
|
}) as T[];
|
|
223
223
|
};
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Finds the index and position where a target value should be inserted into an array
|
|
227
|
+
* of bounds.
|
|
228
|
+
*
|
|
229
|
+
* Crucially, this function assumes that the bounds are ORDERED and NON-OVERLAPPING.
|
|
230
|
+
*
|
|
231
|
+
* @template T
|
|
232
|
+
* @param {Array<Crude<T>>} bounds - An array of crude bounds. Each bound can either be
|
|
233
|
+
* an array of length 2 or an object with `lower` and `upper` properties.
|
|
234
|
+
* @param {T} target - The target value to insert.
|
|
235
|
+
*
|
|
236
|
+
* @returns {{ index: number, position: number }} An object containing:
|
|
237
|
+
* - `index`: The index in the bounds array where the target belongs.
|
|
238
|
+
* - `position`: The position within the bound where the target fits. If the target is
|
|
239
|
+
* outside all bounds, the index will be where a new bound can be inserted.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* // Target within an existing bound
|
|
243
|
+
* const bounds = [[0, 10], [20, 30]];
|
|
244
|
+
* const target = 5;
|
|
245
|
+
* const result = findInsertPosition(bounds, target);
|
|
246
|
+
* // { index: 0, position: 5 }
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* // Target greater than all bounds
|
|
250
|
+
* const bounds = [[0, 10], [20, 30]];
|
|
251
|
+
* const target = 35;
|
|
252
|
+
* const result = findInsertPosition(bounds, target);
|
|
253
|
+
* // { index: 2, position: 0 }
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* // Target less than all bounds
|
|
257
|
+
* const bounds = [[10, 20], [30, 40]];
|
|
258
|
+
* const target = 5;
|
|
259
|
+
* const result = findInsertPosition(bounds, target);
|
|
260
|
+
* // { index: 0, position: 0 }
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* // Target overlaps between bounds
|
|
264
|
+
* const bounds = [[0, 10], [20, 30]];
|
|
265
|
+
* const target = 15;
|
|
266
|
+
* const result = findInsertPosition(bounds, target);
|
|
267
|
+
* // { index: 1, position: 0 }
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* // Empty bounds array
|
|
271
|
+
* const bounds = [];
|
|
272
|
+
* const target = 5;
|
|
273
|
+
* const result = findInsertPosition(bounds, target);
|
|
274
|
+
* // { index: 0, position: 0 }
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* // Target exactly at lower bound
|
|
278
|
+
* const bounds = [[0, 10], [20, 30]];
|
|
279
|
+
* const target = 10;
|
|
280
|
+
* const result = findInsertPosition(bounds, target);
|
|
281
|
+
* // { index: 1, position: 0 }
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* // Target exactly at upper bound
|
|
285
|
+
* const bounds = [[0, 10], [20, 30]];
|
|
286
|
+
* const target = 30;
|
|
287
|
+
* const result = findInsertPosition(bounds, target);
|
|
288
|
+
* // { index: 2, position: 0 }
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* // Target inside bounds with exact fit
|
|
292
|
+
* const bounds = [[0, 5], [5, 10]];
|
|
293
|
+
* const target = 5;
|
|
294
|
+
* const result = findInsertPosition(bounds, target);
|
|
295
|
+
* // { index: 1, position: 0 }
|
|
296
|
+
*
|
|
297
|
+
* @throws {Error} If invalid bounds are provided, such as bounds arrays not being of
|
|
298
|
+
* length 2.
|
|
299
|
+
*
|
|
300
|
+
* See {@link construct} for constructing valid bounds.
|
|
301
|
+
*/
|
|
225
302
|
export const findInsertPosition = <T extends numeric.Value>(
|
|
226
303
|
bounds: Array<Crude<T>>,
|
|
227
304
|
target: T,
|
|
228
305
|
): { index: number; position: number } => {
|
|
229
306
|
const _bounds = bounds.map((b) => construct<T>(b));
|
|
230
|
-
const index = _bounds.findIndex(
|
|
231
|
-
(b, i) => contains<T>(b, target) || target < _bounds[i].lower,
|
|
232
|
-
);
|
|
307
|
+
const index = _bounds.findIndex((b) => contains<T>(b, target) || target < b.lower);
|
|
233
308
|
if (index === -1) return { index: bounds.length, position: 0 };
|
|
234
309
|
const b = _bounds[index];
|
|
235
310
|
if (contains(b, target)) return { index, position: Number(target - b.lower) };
|
|
@@ -265,6 +340,7 @@ const ZERO_PLAN: InsertionPlan = {
|
|
|
265
340
|
* that may overlap. The plan is used to determine how to splice the new array into the
|
|
266
341
|
* existing array. The following are important constraints:
|
|
267
342
|
*
|
|
343
|
+
* Crucially, this function assumes that the bounds are ORDERED and NON-OVERLAPPING.
|
|
268
344
|
*
|
|
269
345
|
* 1. If the new bound is entirely contained within an existing bound, the new bound
|
|
270
346
|
* is not inserted and the plan is null.
|
|
@@ -318,29 +394,218 @@ export const buildInsertionPlan = <T extends numeric.Value>(
|
|
|
318
394
|
};
|
|
319
395
|
};
|
|
320
396
|
|
|
321
|
-
|
|
397
|
+
/**
|
|
398
|
+
* Traverse the given bounds by the specified distance, starting from a given point, and
|
|
399
|
+
* return the end point of the traversal. The traversal 'skips' over integers that are
|
|
400
|
+
* not within the array of bounds, moving only within the defined bounds. Traversing
|
|
401
|
+
* across multiple bounds is handled smoothly, with direction determined by the sign of
|
|
402
|
+
* the distance.
|
|
403
|
+
*
|
|
404
|
+
* Crucially, this function assumes that the bounds are ORDERED and NON-OVERLAPPING.
|
|
405
|
+
*
|
|
406
|
+
* If the distance takes the traversal beyond the bounds, it returns the last valid point
|
|
407
|
+
* within the bounds or the first valid point depending on direction.
|
|
408
|
+
*
|
|
409
|
+
* @template T
|
|
410
|
+
* @param {Array<Crude<T>>} bounds - An array of crude bounds (array of length 2 or
|
|
411
|
+
* objects with `lower` and `upper` properties).
|
|
412
|
+
* @param {T} start - The starting point of the traversal.
|
|
413
|
+
* @param {T} dist - The distance to traverse. Positive values move forwards, and
|
|
414
|
+
* negative values move backwards.
|
|
415
|
+
*
|
|
416
|
+
* Edge Cases:
|
|
417
|
+
*
|
|
418
|
+
* 1. **Traversal beyond the last bound**: If the traversal moves beyond the last
|
|
419
|
+
* bound (in either direction), the traversal ends at the last valid position within
|
|
420
|
+
* the bounds.
|
|
421
|
+
* - Example: `traverse([[0, 10], [20, 30]], 25, 10); // => 30`
|
|
422
|
+
* (stops at the upper limit of the last bound)
|
|
423
|
+
*
|
|
424
|
+
* 2. **Traversal from a point outside the bounds**: If the starting point is outside
|
|
425
|
+
* the bounds and the traversal distance would move within bounds, it finds the
|
|
426
|
+
* closest bound and continues traversal from there.
|
|
427
|
+
* - Example: `traverse([[0, 10], [20, 30]], 15, 5); // => 25` (enters the second bound)
|
|
428
|
+
*
|
|
429
|
+
* 3. **Distance of 0**: If the distance is `0`, the traversal will return the starting
|
|
430
|
+
* point without moving.
|
|
431
|
+
* - Example: `traverse([[0, 10], [20, 30]], 5, 0); // => 5`
|
|
432
|
+
*
|
|
433
|
+
* @returns {T} The end point of the traversal within the bounds.
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* // Traversing 5 units forward from 5, ending exactly at the upper bound of the first
|
|
437
|
+
* range.
|
|
438
|
+
* traverse([[0, 10], [20, 30]], 5, 5);
|
|
439
|
+
* // => 10
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* // Traversing 10 units forward from 5, crossing from the first range to the second.
|
|
443
|
+
* traverse([[0, 10], [20, 30]], 5, 10);
|
|
444
|
+
* // => 25
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* // Traversing 5 units forward starting outside the bounds, the traversal enters the
|
|
448
|
+
* // second bound.
|
|
449
|
+
* traverse([[0, 10], [20, 30]], 15, 5);
|
|
450
|
+
* // => 25
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* // Traversing 30 units forward, stopping at the upper end of the second bound.
|
|
454
|
+
* traverse([[0, 10], [20, 30]], 15, 30);
|
|
455
|
+
* // => 30
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* // Traversing 7 units backward starting from 17, moving into the first bound.
|
|
459
|
+
* traverse([[0, 5], [5, 10], [15, 20]], 17, -7);
|
|
460
|
+
* // => 5
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* // Traversing beyond the last bound in a positive direction.
|
|
464
|
+
* traverse([[0, 10], [20, 30]], 25, 10);
|
|
465
|
+
* // => 30 (stops at the upper limit of the last bound)
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* // Traversing backward from a point not within any bound.
|
|
469
|
+
* traverse([[0, 5], [10, 15]], 20, -10);
|
|
470
|
+
* // => 15 (stops at the upper limit of the nearest previous bound)
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* // Traversing a distance of 0 from a point returns the starting point.
|
|
474
|
+
* traverse([[0, 10], [20, 30]], 5, 0);
|
|
475
|
+
* // => 5
|
|
476
|
+
*
|
|
477
|
+
* @throws {Error} If invalid bounds are provided, such as bounds arrays not being of
|
|
478
|
+
* length 2.
|
|
479
|
+
*
|
|
480
|
+
* See {@link construct} for constructing valid bounds.
|
|
481
|
+
*
|
|
482
|
+
*/
|
|
483
|
+
export const traverse = <T extends numeric.Value = number>(
|
|
322
484
|
bounds: Array<Crude<T>>,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
485
|
+
start: T,
|
|
486
|
+
dist: T,
|
|
487
|
+
): T => {
|
|
488
|
+
const _bounds = bounds.map((b) => construct(b));
|
|
489
|
+
|
|
490
|
+
const dir = dist > 0 ? 1 : dist < 0 ? -1 : 0;
|
|
491
|
+
|
|
492
|
+
// If there's no distance to traverse, return the starting point
|
|
493
|
+
if (dir === 0) return start;
|
|
494
|
+
|
|
495
|
+
let remainingDist = dist;
|
|
496
|
+
let currentPosition = start as number | bigint;
|
|
497
|
+
|
|
498
|
+
while (math.equal(remainingDist, 0) === false) {
|
|
499
|
+
// Find the bound we're currently in or adjacent to
|
|
500
|
+
const index = _bounds.findIndex((b) => {
|
|
501
|
+
if (dir > 0) return currentPosition >= b.lower && currentPosition < b.upper;
|
|
502
|
+
return currentPosition > b.lower && currentPosition <= b.upper;
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
if (index !== -1) {
|
|
506
|
+
const b = _bounds[index];
|
|
507
|
+
let distanceInBound: T;
|
|
508
|
+
if (dir > 0) distanceInBound = math.sub(b.upper, currentPosition) as T;
|
|
509
|
+
else distanceInBound = math.sub(currentPosition, b.lower) as T;
|
|
510
|
+
|
|
511
|
+
if (distanceInBound > (0 as T)) {
|
|
512
|
+
const moveDist = math.min(math.abs(remainingDist), distanceInBound) as T;
|
|
513
|
+
currentPosition = math.add(
|
|
514
|
+
currentPosition,
|
|
515
|
+
dir > 0 ? moveDist : -moveDist,
|
|
516
|
+
) as T;
|
|
517
|
+
remainingDist = math.sub(remainingDist, dir > 0 ? moveDist : -moveDist) as T;
|
|
518
|
+
|
|
519
|
+
// If we've exhausted the distance, return the current position
|
|
520
|
+
if (math.equal(remainingDist, 0)) return currentPosition as T;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// If we're not inside any bound, or we've reached the boundary
|
|
526
|
+
if (dir > 0) {
|
|
527
|
+
// Move to the next bound's lower value
|
|
528
|
+
const nextBounds = _bounds.filter((b) => b.lower > currentPosition);
|
|
529
|
+
if (nextBounds.length > 0) currentPosition = nextBounds[0].lower;
|
|
530
|
+
// No more bounds in this direction
|
|
531
|
+
else return currentPosition as T;
|
|
532
|
+
} else {
|
|
533
|
+
// Move to the previous bound's upper value
|
|
534
|
+
const prevBounds = _bounds.filter((b) => b.upper < currentPosition);
|
|
535
|
+
if (prevBounds.length > 0)
|
|
536
|
+
currentPosition = prevBounds[prevBounds.length - 1].upper;
|
|
537
|
+
// No more bounds in this direction
|
|
538
|
+
else return currentPosition as T;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return currentPosition as T;
|
|
333
542
|
};
|
|
334
543
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
544
|
+
/**
|
|
545
|
+
* Returns the number of values within the given bounds, 'skip'ing over values that are
|
|
546
|
+
* not within the bounds.
|
|
547
|
+
*
|
|
548
|
+
* Crucially, this function assumes that the bounds are ORDERED and NON-OVERLAPPING.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* bounds.distance(
|
|
552
|
+
* [[0, 10], [20, 30]]
|
|
553
|
+
* 5,
|
|
554
|
+
* 5,
|
|
555
|
+
* ) // => 0
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* bounds.distance(
|
|
559
|
+
* [[0, 10], [20, 30]]
|
|
560
|
+
* 5,
|
|
561
|
+
* 25,
|
|
562
|
+
* ) // => 10
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* bounds.distance(
|
|
566
|
+
* [[0, 10], [20, 30]]
|
|
567
|
+
* 15,
|
|
568
|
+
* 25,
|
|
569
|
+
* ) // => 5
|
|
570
|
+
*
|
|
571
|
+
* @example
|
|
572
|
+
* bounds.distance(
|
|
573
|
+
* [[0, 10], [20, 30]]
|
|
574
|
+
* 15,
|
|
575
|
+
* 5,
|
|
576
|
+
* ) // => 5
|
|
577
|
+
*
|
|
578
|
+
* @param bounds
|
|
579
|
+
* @param a - The start value.
|
|
580
|
+
* @param b - The end value.
|
|
581
|
+
*/
|
|
582
|
+
export const distance = <T extends numeric.Value = number>(
|
|
583
|
+
bounds: Array<Crude<T>>,
|
|
584
|
+
a: T,
|
|
585
|
+
b: T,
|
|
586
|
+
): T => {
|
|
587
|
+
const _bounds = bounds.map((b) => construct<T>(b));
|
|
588
|
+
|
|
589
|
+
// If start and end are the same, the distance is zero
|
|
590
|
+
if (a === b) return (typeof a === "bigint" ? 0n : 0) as T;
|
|
591
|
+
|
|
592
|
+
// Determine the interval between a and b
|
|
593
|
+
const interval = a < b ? construct([a, b]) : construct([b, a]);
|
|
594
|
+
|
|
595
|
+
let totalDistance: T = (typeof a === "bigint" ? 0n : 0) as T;
|
|
596
|
+
|
|
597
|
+
for (const bound of _bounds) {
|
|
598
|
+
// Find the overlap between the interval and the current bound
|
|
599
|
+
const overlapLower = bound.lower > interval.lower ? bound.lower : interval.lower;
|
|
600
|
+
const overlapUpper = bound.upper < interval.upper ? bound.upper : interval.upper;
|
|
601
|
+
|
|
602
|
+
// If there is an overlap, add its span to the total distance
|
|
603
|
+
if (overlapLower < overlapUpper) {
|
|
604
|
+
const overlapSpan = (overlapUpper - overlapLower) as T;
|
|
605
|
+
// @ts-expect-error - typescript doesn't recognize that totalDistance is a number
|
|
606
|
+
totalDistance = (totalDistance + overlapSpan) as T;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return totalDistance;
|
|
346
611
|
};
|
package/src/spatial/box/box.ts
CHANGED
|
@@ -17,13 +17,13 @@ import * as xy from "@/spatial/xy/xy";
|
|
|
17
17
|
|
|
18
18
|
const cssPos = z.union([z.number(), z.string()]);
|
|
19
19
|
|
|
20
|
-
const cssBox = z.object({
|
|
20
|
+
export const cssBox = z.object({
|
|
21
21
|
top: cssPos,
|
|
22
22
|
left: cssPos,
|
|
23
23
|
width: cssPos,
|
|
24
24
|
height: cssPos,
|
|
25
25
|
});
|
|
26
|
-
const domRect = z.object({
|
|
26
|
+
export const domRect = z.object({
|
|
27
27
|
left: z.number(),
|
|
28
28
|
top: z.number(),
|
|
29
29
|
right: z.number(),
|
|
@@ -60,7 +60,7 @@ export const copy = (b: Box, root?: location.CornerXY): Box => ({
|
|
|
60
60
|
* Box represents a general box in 2D space. It typically represents a bounding box
|
|
61
61
|
* for a DOM element, but can also represent a box in clip space or decimal space.
|
|
62
62
|
*
|
|
63
|
-
* It'
|
|
63
|
+
* It's important to note that the behavior of a Box varies depending on its coordinate
|
|
64
64
|
* system.Make sure you're aware of which coordinate system you're using.
|
|
65
65
|
*
|
|
66
66
|
* Many of the properties and methods on a Box access the same semantic value. The
|
|
@@ -157,7 +157,11 @@ export const resize: Resize = (
|
|
|
157
157
|
* @param inclusive - Whether the edges of the box are inclusive or exclusive.
|
|
158
158
|
* @returns true if the box inclusively contains the point or box and false otherwise.
|
|
159
159
|
*/
|
|
160
|
-
export const contains = (
|
|
160
|
+
export const contains = (
|
|
161
|
+
container: Crude,
|
|
162
|
+
value: Box | xy.XY,
|
|
163
|
+
inclusive: boolean = true,
|
|
164
|
+
): boolean => {
|
|
161
165
|
const b_ = construct(container);
|
|
162
166
|
let comp = (a: number, b: number) => a < b;
|
|
163
167
|
if (inclusive) comp = (a: number, b: number) => a <= b;
|
|
@@ -352,7 +356,7 @@ export const edgePoints = (b: Crude, loc: location.Location): [xy.XY, xy.XY] =>
|
|
|
352
356
|
*
|
|
353
357
|
* @param target The box to reposition - Only works if the root is topLeft
|
|
354
358
|
* @param bound The box to reposition within - Only works if the root is topLeft
|
|
355
|
-
* @returns the
|
|
359
|
+
* @returns the repositioned box
|
|
356
360
|
*/
|
|
357
361
|
export const positionInCenter = (target_: Crude, bound_: Crude): Box => {
|
|
358
362
|
const target = construct(target_);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it } from "vitest";
|
|
11
11
|
|
|
12
|
-
import { naturalLanguageJoin } from "@/strings/strings";
|
|
12
|
+
import { generateShortIdentifiers, naturalLanguageJoin } from "@/strings/strings";
|
|
13
13
|
|
|
14
14
|
describe("naturalLanguageJoin", () => {
|
|
15
15
|
it("should return an empty string for an empty array", () =>
|
|
@@ -34,3 +34,35 @@ describe("naturalLanguageJoin", () => {
|
|
|
34
34
|
"apple, banana, cherry, and date",
|
|
35
35
|
));
|
|
36
36
|
});
|
|
37
|
+
|
|
38
|
+
describe("generateShortIdentifiers", () => {
|
|
39
|
+
it("should generate identifiers for a single word", () =>
|
|
40
|
+
expect(generateShortIdentifiers("Bob")).toEqual(expect.arrayContaining(["bob"])));
|
|
41
|
+
|
|
42
|
+
it("should generate identifiers for multiple words", () =>
|
|
43
|
+
expect(generateShortIdentifiers("John Doe")).toEqual(
|
|
44
|
+
expect.arrayContaining(["jd", "j_d", "johdoe", "joh_doe"]),
|
|
45
|
+
));
|
|
46
|
+
|
|
47
|
+
it("should generate identifiers for words containing numbers", () =>
|
|
48
|
+
expect(generateShortIdentifiers("Alice 123")).toEqual(
|
|
49
|
+
expect.arrayContaining(["a1", "a_1", "ali123", "ali_123"]),
|
|
50
|
+
));
|
|
51
|
+
|
|
52
|
+
it("should generate identifiers for words longer than three characters", () =>
|
|
53
|
+
expect(generateShortIdentifiers("Jonathan")).toEqual(
|
|
54
|
+
expect.arrayContaining(["jon"]),
|
|
55
|
+
));
|
|
56
|
+
|
|
57
|
+
it("should generate identifiers for words shorter than three characters", () =>
|
|
58
|
+
expect(generateShortIdentifiers("Al")).toEqual(expect.arrayContaining(["al"])));
|
|
59
|
+
|
|
60
|
+
it("should generate identifiers for mixed cases", () =>
|
|
61
|
+
expect(generateShortIdentifiers("Alice Bob")).toEqual(
|
|
62
|
+
expect.arrayContaining(["ab", "a_b", "alibob", "ali_bob"]),
|
|
63
|
+
));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log(generateShortIdentifiers("John Doe")); // ["jd", "j_d", "johdoe", "joh_doe"]
|
|
67
|
+
console.log(generateShortIdentifiers("Alice 123")); // ["a1", "a_1", "ali123", "ali_123"]
|
|
68
|
+
console.log(generateShortIdentifiers("Bob")); // ["bob"]
|
package/src/strings/strings.ts
CHANGED
|
@@ -33,3 +33,55 @@ export const naturalLanguageJoin = (
|
|
|
33
33
|
if (length === 2) return `${strings[0]} and ${strings[1]}`;
|
|
34
34
|
return `${strings.slice(0, -1).join(", ")}, and ${strings[length - 1]}`;
|
|
35
35
|
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generates a list of short identifiers from a given name.
|
|
39
|
+
*
|
|
40
|
+
* @param name - The name to generate identifiers from.
|
|
41
|
+
* @returns An array of unique short identifiers.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* generateShortIdentifiers("John Doe"); // ["jd", "j_d", "johdoe", "joh_doe"]
|
|
46
|
+
* generateShortIdentifiers("Alice 123"); // ["a1", "a_1", "a123", "a_12_3", "ali123", "ali_123"]
|
|
47
|
+
* generateShortIdentifiers("Bob"); // ["bob"]
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export const generateShortIdentifiers = (name: string): string[] => {
|
|
51
|
+
const words = name.split(" ");
|
|
52
|
+
const identifiers = new Set<string>();
|
|
53
|
+
|
|
54
|
+
// Generate initials
|
|
55
|
+
const initials = words.map((word) => word.charAt(0).toLowerCase()).join("");
|
|
56
|
+
identifiers.add(initials);
|
|
57
|
+
identifiers.add(initials.replace(/(.)(.)/g, "$1_$2")); // Insert underscores
|
|
58
|
+
|
|
59
|
+
// Generate combinations with numbers
|
|
60
|
+
const regex = /\d+/g;
|
|
61
|
+
const hasNumbers = name.match(regex);
|
|
62
|
+
|
|
63
|
+
if (hasNumbers)
|
|
64
|
+
words.forEach((word, index) => {
|
|
65
|
+
if (regex.test(word)) {
|
|
66
|
+
const abbreviatedWords = words
|
|
67
|
+
.map((w, i) => (i !== index ? w.charAt(0).toLowerCase() : w))
|
|
68
|
+
.join("");
|
|
69
|
+
identifiers.add(abbreviatedWords);
|
|
70
|
+
identifiers.add(abbreviatedWords.replace(/(.)(.)/g, "$1_$2")); // Insert underscores
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Generate other potential combinations
|
|
75
|
+
const wordAbbreviations = words.map((word) =>
|
|
76
|
+
(word.length > 3 ? word.substring(0, 3) : word).toLowerCase(),
|
|
77
|
+
);
|
|
78
|
+
identifiers.add(wordAbbreviations.join(""));
|
|
79
|
+
identifiers.add(wordAbbreviations.join("_"));
|
|
80
|
+
|
|
81
|
+
// Limit length of identifiers
|
|
82
|
+
const filteredIdentifiers = Array.from(identifiers).filter(
|
|
83
|
+
(id) => id.length >= 2 && id.length <= 12,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return filteredIdentifiers;
|
|
87
|
+
};
|