@libs-ui/utils 0.2.282 → 0.2.283

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.
@@ -645,6 +645,70 @@ const omitBy = (objData, predicate) => {
645
645
  });
646
646
  return newObj;
647
647
  };
648
+ /**
649
+ * Lấy giá trị từ đối tượng theo đường dẫn chỉ định
650
+ *
651
+ * Hàm này giúp bạn truy cập vào các thuộc tính sâu bên trong một đối tượng một cách an toàn,
652
+ * tránh lỗi khi thuộc tính không tồn tại.
653
+ *
654
+ * @param obj Đối tượng nguồn cần lấy giá trị
655
+ * @param path Đường dẫn đến thuộc tính cần lấy. Có thể là:
656
+ * - Chuỗi: 'user.profile.name' hoặc 'items[0].title'
657
+ * - Mảng: ['user', 'profile', 'name'] hoặc ['items', '0', 'title']
658
+ * - Chuỗi rỗng '': trả về chính đối tượng gốc
659
+ * @param defaultValue Giá trị mặc định trả về khi không tìm thấy thuộc tính (mặc định: undefined)
660
+ * @param keepLastValueIfSignal Có giữ nguyên signal cuối cùng hay không (mặc định: false - sẽ gọi signal())
661
+ * @returns Giá trị tìm được hoặc giá trị mặc định
662
+ *
663
+ * @example
664
+ * // Ví dụ cơ bản
665
+ * const user = { name: 'John', age: 30 };
666
+ * get(user, 'name'); // 'John'
667
+ * get(user, 'email'); // undefined
668
+ * get(user, 'email', 'no-email'); // 'no-email'
669
+ *
670
+ * @example
671
+ * // Truyền path rỗng - trả về chính đối tượng gốc
672
+ * const data = { name: 'Alice', age: 25 };
673
+ * get(data, ''); // { name: 'Alice', age: 25 } (chính đối tượng data)
674
+ * get(data, '', 'default'); // { name: 'Alice', age: 25 } (bỏ qua defaultValue)
675
+ *
676
+ * @example
677
+ * // Truy cập thuộc tính sâu
678
+ * const data = {
679
+ * user: {
680
+ * profile: {
681
+ * name: 'Alice',
682
+ * settings: { theme: 'dark' }
683
+ * }
684
+ * }
685
+ * };
686
+ * get(data, 'user.profile.name'); // 'Alice'
687
+ * get(data, 'user.profile.settings.theme'); // 'dark'
688
+ * get(data, 'user.profile.avatar', 'default.jpg'); // 'default.jpg'
689
+ *
690
+ * @example
691
+ * // Truy cập mảng
692
+ * const items = [
693
+ * { name: 'Item 1', price: 100 },
694
+ * { name: 'Item 2', price: 200 }
695
+ * ];
696
+ * get(items, '[0].name'); // 'Item 1'
697
+ * get(items, '0.price'); // 100
698
+ * get(items, '[1].name'); // 'Item 2'
699
+ * get(items, '[2].name', 'Not found'); // 'Not found'
700
+ *
701
+ * @example
702
+ * // Sử dụng với mảng path
703
+ * const nested = { a: { b: { c: 'deep value' } } };
704
+ * get(nested, ['a', 'b', 'c']); // 'deep value'
705
+ * get(nested, ['a', 'b', 'd'], 'default'); // 'default'
706
+ *
707
+ * @example
708
+ * // Trường hợp đặc biệt
709
+ * get(null, 'any.path'); // undefined
710
+ * get(undefined, 'any.path', 'fallback'); // 'fallback'
711
+ */
648
712
  const get = (obj, path, defaultValue = undefined, keepLastValueIfSignal) => {
649
713
  if (isNil(obj)) {
650
714
  return defaultValue;
@@ -2447,32 +2511,57 @@ const convertUrlToFile = (url, fileName) => {
2447
2511
  });
2448
2512
  };
2449
2513
 
2514
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2515
+ // Constants for better maintainability
2516
+ const DEFAULT_MIN_TICK_COUNT = 5;
2517
+ const DEFAULT_MAX_TICK_COUNT = 10;
2518
+ const MIN_DISTANCE_TO_ZERO = 3;
2519
+ const NEGATIVE_THRESHOLD = -5;
2520
+ const FALLBACK_NEGATIVE_VALUE = -4;
2521
+ const MAX_POW_NUMBER = 14;
2522
+ const MAX_TEMPLATE_NUMBER = 10;
2523
+ // Cache for power calculations - shared across function calls for better performance
2524
+ const POWER_CACHE = new Map();
2450
2525
  /**
2451
- * Hàm thể trả về lỗi nếu:
2452
- * - maxData < 0 và acceptNegative là false
2453
- * - minNegative undefined acceptNegative là true
2454
- * - maxData < minNegative (chỉ xảy ra khi acceptNegative true)
2455
- * - minNegative > 0 (chỉ xảy ra khi acceptNegative true)
2456
- * @throws {Error}
2457
- * @required luôn đặt hàm này trong try catch và xử lý ngoại lệ
2458
- * @hint Truyền 3 tham số: min,max và tick
2526
+ * Calculates smart axis scale for charts
2527
+ *
2528
+ * @description This function can throw errors if:
2529
+ * - maxData < 0 and acceptNegative is false
2530
+ * - minNegative is undefined and acceptNegative is true
2531
+ * - maxData < minNegative (only when acceptNegative is true)
2532
+ * - minNegative > 0 (only when acceptNegative is true)
2533
+ *
2534
+ * @throws {Error} Validation errors for invalid input combinations
2535
+ * @required Always wrap this function in try-catch and handle exceptions
2536
+ * @param maxData - Maximum data value
2537
+ * @param options - Configuration options for axis scaling
2538
+ * @returns Axis scale configuration
2459
2539
  */
2460
2540
  const getSmartAxisScale = (maxData, options) => {
2461
- const minTickCount = options?.minTickCount || 5;
2462
- const maxTickCount = options?.maxTickCount || 10;
2463
- const stepCandidatesDefault = new Array();
2464
- const loopStepCandidates = Array.from({ length: 10 }, (_, index) => Math.pow(10, index)).findIndex(item => item > Math.abs(Math.max(maxData, Math.abs(options?.minNegative || 0)))) + 1;
2465
- Array.from({ length: loopStepCandidates }, (_, index) => {
2466
- for (let templateNumber = 1; templateNumber < 10; templateNumber++) {
2467
- if (options?.acceptStepIsTypeFloat) {
2468
- stepCandidatesDefault.push(Math.pow(10, index) * (((templateNumber - 1) * 2 + 1) / 2));
2469
- }
2470
- stepCandidatesDefault.push(Math.pow(10, index) * templateNumber);
2471
- }
2472
- });
2473
- const stepCandidates = options?.stepCandidates || stepCandidatesDefault;
2474
- let distanceToZero = maxData;
2475
- let reverse = 1;
2541
+ // Input validation - fail fast approach
2542
+ validateInputs(maxData, options);
2543
+ // Extract and set defaults
2544
+ const { minTickCount = DEFAULT_MIN_TICK_COUNT, maxTickCount = DEFAULT_MAX_TICK_COUNT, stepCandidates = [], acceptNegative = false, minNegative, acceptStepIsTypeFloat = false } = options || {};
2545
+ // Generate step candidates if not provided
2546
+ const finalStepCandidates = stepCandidates.length > 0
2547
+ ? [...stepCandidates]
2548
+ : generateStepCandidates(maxData, minTickCount, minNegative, acceptStepIsTypeFloat);
2549
+ // Remove console.log for production code
2550
+ // console.log(`stepCandidates: `, finalStepCandidates);
2551
+ // Calculate scale parameters
2552
+ const scaleParams = calculateScaleParams(maxData, acceptNegative, minNegative, finalStepCandidates);
2553
+ // Find optimal scale
2554
+ const optimalScale = findOptimalScale(scaleParams, finalStepCandidates, minTickCount, maxTickCount, options);
2555
+ if (optimalScale) {
2556
+ return optimalScale;
2557
+ }
2558
+ // Fallback calculation
2559
+ return calculateFallbackScale(scaleParams.distanceToZero, minTickCount);
2560
+ };
2561
+ /**
2562
+ * Validates input parameters
2563
+ */
2564
+ function validateInputs(maxData, options) {
2476
2565
  if (maxData < 0 && !options?.acceptNegative) {
2477
2566
  throw new Error("maxData is less than 0 and acceptNegative is false");
2478
2567
  }
@@ -2486,48 +2575,86 @@ const getSmartAxisScale = (maxData, options) => {
2486
2575
  if (options.minNegative >= 0) {
2487
2576
  throw new Error("minNegative must be negative");
2488
2577
  }
2578
+ }
2579
+ }
2580
+ /**
2581
+ * Calculates scale parameters for axis calculation
2582
+ */
2583
+ function calculateScaleParams(maxData, acceptNegative, minNegative, stepCandidates) {
2584
+ let distanceToZero = maxData;
2585
+ let reverse = 1;
2586
+ let adjustedMaxData = maxData;
2587
+ if (acceptNegative && !isNil(minNegative)) {
2489
2588
  if (maxData === 0) {
2490
- maxData = -1;
2589
+ adjustedMaxData = -1;
2590
+ }
2591
+ // Add negative step candidates
2592
+ if (stepCandidates) {
2593
+ stepCandidates.unshift(...stepCandidates.map(item => -item));
2491
2594
  }
2492
- stepCandidates.unshift(...stepCandidates.map(item => item * -1));
2493
- if (maxData <= 0) {
2494
- distanceToZero = options.minNegative;
2595
+ if (adjustedMaxData <= 0) {
2596
+ distanceToZero = minNegative;
2495
2597
  }
2496
- if (distanceToZero > -5) {
2497
- distanceToZero = -4;
2598
+ if (distanceToZero > NEGATIVE_THRESHOLD) {
2599
+ distanceToZero = FALLBACK_NEGATIVE_VALUE;
2498
2600
  }
2499
- if (maxData > 0) {
2500
- distanceToZero = maxData + Math.abs(options.minNegative);
2601
+ if (adjustedMaxData > 0) {
2602
+ distanceToZero = adjustedMaxData + Math.abs(minNegative);
2501
2603
  reverse = -1;
2502
2604
  }
2503
2605
  }
2504
- if (Math.abs(distanceToZero) < 3) {
2505
- distanceToZero = 3;
2606
+ if (Math.abs(distanceToZero) < MIN_DISTANCE_TO_ZERO) {
2607
+ distanceToZero = MIN_DISTANCE_TO_ZERO;
2506
2608
  }
2609
+ return { distanceToZero, reverse, adjustedMaxData };
2610
+ }
2611
+ /**
2612
+ * Finds optimal scale from step candidates
2613
+ */
2614
+ function findOptimalScale(scaleParams, stepCandidates, minTickCount, maxTickCount, options) {
2615
+ const { distanceToZero, reverse, adjustedMaxData } = scaleParams;
2507
2616
  for (const step of stepCandidates) {
2508
2617
  let tickCount = Math.abs(Math.ceil(distanceToZero / step)) + (reverse === -1 ? 2 : 1);
2509
- let maxValue = maxData <= 0 ? 0 : step * tickCount * reverse;
2618
+ let maxValue = adjustedMaxData <= 0 ? 0 : step * tickCount * reverse;
2510
2619
  let minValue = 0;
2511
- if (tickCount >= minTickCount && tickCount <= maxTickCount) {
2512
- if (maxData < maxValue) {
2513
- if (options?.acceptNegative) {
2514
- let tick = 1;
2515
- while (!isNil(options.minNegative) && tick <= tickCount && minValue >= options.minNegative) {
2516
- minValue = tick * step;
2517
- tick++;
2518
- }
2519
- }
2520
- tickCount = distanceToZero - (tickCount - 1) * step < -1 * (step / 4) ? tickCount - 1 : tickCount;
2521
- maxValue = (step * tickCount * reverse) - (minValue * reverse);
2522
- return {
2523
- stepSize: Math.abs(step),
2524
- max: maxValue,
2525
- min: minValue,
2526
- tickAmount: tickCount
2527
- };
2620
+ if (tickCount >= minTickCount && tickCount <= maxTickCount && adjustedMaxData < maxValue) {
2621
+ if (options?.acceptNegative) {
2622
+ minValue = calculateMinValue(step, tickCount, options.minNegative);
2528
2623
  }
2624
+ // Adjust tick count based on distance calculation
2625
+ const distanceCheck = distanceToZero - (tickCount - 1) * step;
2626
+ if (distanceCheck < -1 * (step / 4)) {
2627
+ tickCount -= 1;
2628
+ }
2629
+ maxValue = (step * tickCount * reverse) - (minValue * reverse);
2630
+ return {
2631
+ stepSize: Math.abs(step),
2632
+ max: maxValue,
2633
+ min: minValue,
2634
+ tickAmount: tickCount
2635
+ };
2636
+ }
2637
+ }
2638
+ return null;
2639
+ }
2640
+ /**
2641
+ * Calculates minimum value for negative acceptance
2642
+ */
2643
+ function calculateMinValue(step, tickCount, minNegative) {
2644
+ let minValue = 0;
2645
+ if (!isNil(minNegative)) {
2646
+ let tick = 1;
2647
+ while (tick <= tickCount && minValue >= minNegative) {
2648
+ minValue = tick * step;
2649
+ tick++;
2529
2650
  }
2530
2651
  }
2652
+ return minValue;
2653
+ }
2654
+ /**
2655
+ * Calculates fallback scale when no optimal scale is found
2656
+ */
2657
+ function calculateFallbackScale(distanceToZero, minTickCount) {
2531
2658
  let step = Math.ceil(distanceToZero / minTickCount) || 1;
2532
2659
  let maxValue = step * minTickCount;
2533
2660
  if (distanceToZero === maxValue) {
@@ -2540,7 +2667,34 @@ const getSmartAxisScale = (maxData, options) => {
2540
2667
  min: 0,
2541
2668
  tickAmount: minTickCount
2542
2669
  };
2543
- };
2670
+ }
2671
+ /**
2672
+ * Generates step candidates with performance optimizations
2673
+ */
2674
+ function generateStepCandidates(maxData, minStep, minNegative, acceptStepIsTypeFloat) {
2675
+ const stepCandidates = [];
2676
+ const maxValueStep = Math.abs(Math.max(maxData, Math.abs(minNegative || 0))) / minStep;
2677
+ // Pre-populate power cache for better performance
2678
+ for (let powNumber = 0; powNumber < MAX_POW_NUMBER; powNumber++) {
2679
+ if (!POWER_CACHE.has(powNumber)) {
2680
+ POWER_CACHE.set(powNumber, Math.pow(10, powNumber));
2681
+ }
2682
+ const powByTemp = POWER_CACHE.get(powNumber);
2683
+ for (let templateNumber = 1; templateNumber < MAX_TEMPLATE_NUMBER; templateNumber++) {
2684
+ if (acceptStepIsTypeFloat) {
2685
+ const floatStep = powByTemp * (((templateNumber - 1) * 2 + 1) / 2);
2686
+ stepCandidates.push(floatStep);
2687
+ }
2688
+ const step = powByTemp * templateNumber;
2689
+ stepCandidates.push(step);
2690
+ // Early termination for performance
2691
+ if (step >= maxValueStep) {
2692
+ return stepCandidates;
2693
+ }
2694
+ }
2695
+ }
2696
+ return stepCandidates;
2697
+ }
2544
2698
 
2545
2699
  /**
2546
2700
  * Generated bundle index. Do not edit.