nfx-ui 0.8.0 → 0.9.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/dist/src/utils/colors.d.ts +4 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/random.d.ts +39 -0
- package/dist/utils.cjs +1 -1
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.mjs +171 -147
- package/dist/utils.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -35,3 +35,7 @@ export declare const hexToRGBA: (hex: string, alpha: number) => string;
|
|
|
35
35
|
* @returns rgb(r, g, b) 字符串
|
|
36
36
|
*/
|
|
37
37
|
export declare const interpolateColor: (start: string, end: string, factor: number) => string;
|
|
38
|
+
/**
|
|
39
|
+
* Pick one color from a string pool; fallback to white.
|
|
40
|
+
*/
|
|
41
|
+
export declare const pickColor: (pool: string[]) => string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Random utilities (no color logic).
|
|
3
|
+
* 用于项目的随机数/随机选择工具(不包含颜色选择,颜色相关请看 `utils/colors.ts`)。
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generate a floating number in range [min, max).
|
|
7
|
+
* 生成区间为 [min, max) 的浮点数。
|
|
8
|
+
*/
|
|
9
|
+
export declare const randomBetween: (min: number, max: number) => number;
|
|
10
|
+
/**
|
|
11
|
+
* Generate a random integer in range [min, maxInclusive].
|
|
12
|
+
* 生成区间为 [min, maxInclusive] 的整数。
|
|
13
|
+
*/
|
|
14
|
+
export declare const randomInt: (minInclusive: number, maxInclusive: number) => number;
|
|
15
|
+
/**
|
|
16
|
+
* Randomly return a boolean.
|
|
17
|
+
* @param probability True probability in range [0, 1] (default 0.5).
|
|
18
|
+
*/
|
|
19
|
+
export declare const randomBool: (probability?: number) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Alias of `randomBool` (chance).
|
|
22
|
+
* @param probability True probability in range [0, 1] (default 0.5).
|
|
23
|
+
*/
|
|
24
|
+
export declare const chance: (probability?: number) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Randomly return either -1 or 1.
|
|
27
|
+
*/
|
|
28
|
+
export declare const randomSign: () => number;
|
|
29
|
+
/**
|
|
30
|
+
* Randomly pick one element from a pool.
|
|
31
|
+
* 从数组/只读数组中随机取一个元素。
|
|
32
|
+
*/
|
|
33
|
+
export declare const pickRandom: <T>(pool: readonly T[]) => T;
|
|
34
|
+
/**
|
|
35
|
+
* Generate a Gaussian (normal) distributed number using Box-Muller.
|
|
36
|
+
* @param mean Mean of the normal distribution.
|
|
37
|
+
* @param stdDev Standard deviation (must be > 0).
|
|
38
|
+
*/
|
|
39
|
+
export declare const randomGaussian: (mean?: number, stdDev?: number) => number;
|
package/dist/utils.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
(function(){try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(`.styles-module__loading___bgEAk{justify-content:center;align-items:center;display:inline-flex}.styles-module__loading___bgEAk svg polyline{fill:none;stroke-width:3px;stroke-linecap:round;stroke-linejoin:round}.styles-module__loading___bgEAk svg polyline.styles-module__back___XLPvD{fill:none;stroke:var(--color-primary-alpha,#ff4d5033)}.styles-module__loading___bgEAk svg polyline.styles-module__front___hSxig{fill:none;stroke:var(--color-primary,#ff4d4f);stroke-dasharray:48 144;stroke-dashoffset:192px;animation:1.4s linear infinite styles-module__dash_682___AQzo6}@keyframes styles-module__dash_682___AQzo6{72.5%{opacity:0}to{stroke-dashoffset:0}}.styles-module__loader___E7OIM{justify-content:center;align-items:center;width:fit-content;height:fit-content;display:flex}.styles-module__truckWrapper___Sk4zX{flex-direction:column;justify-content:flex-end;align-items:center;width:100%;height:100%;display:flex;position:relative;overflow-x:hidden}.styles-module__truckBody___j7w2C{width:65%;height:fit-content;margin-bottom:6px;animation:1s linear infinite styles-module__motion___IiNlW}@keyframes styles-module__motion___IiNlW{0%{transform:translateY(0)}50%{transform:translateY(3px)}to{transform:translateY(0)}}.styles-module__truckTires___4ncTl{justify-content:space-between;align-items:center;width:65%;height:fit-content;padding:0 10px 0 15px;display:flex;position:absolute;bottom:0}.styles-module__tiresvg___IBQcN{width:24px}.styles-module__road___sxx-E{background-color:var(--color-fg-heading);border-radius:3px;align-self:flex-end;width:100%;height:1.5px;position:relative;bottom:0}.styles-module__road___sxx-E:before{content:"";background-color:var(--color-fg-heading);border-left:10px solid var(--color-bg);border-radius:3px;width:20px;height:100%;animation:1.4s linear infinite styles-module__roadAnimation___yvrHP;position:absolute;right:-50%}.styles-module__road___sxx-E:after{content:"";background-color:var(--color-fg-heading);border-left:4px solid var(--color-bg);border-radius:3px;width:10px;height:100%;animation:1.4s linear infinite styles-module__roadAnimation___yvrHP;position:absolute;right:-65%}.styles-module__lampPost___okcN5{height:45%;animation:1.4s linear infinite styles-module__roadAnimation___yvrHP;position:absolute;bottom:0;right:-90%}@keyframes styles-module__roadAnimation___yvrHP{0%{transform:translate(0)}to{transform:translate(-350px)}}.styles-module__loader___pFUzL{justify-content:center;align-items:center;display:inline-flex;position:relative}.styles-module__loader___pFUzL:before{content:"";background:var(--color-primary);opacity:.3;border-radius:50%;width:100%;height:5px;animation:.5s linear infinite styles-module__shadow324___sutUe;position:absolute;top:calc(100% + 12px);left:0}.styles-module__loader___pFUzL:after{content:"";background:var(--color-primary);width:100%;height:100%;position:absolute;top:0;left:0}.styles-module__square___GKjhm:after{border-radius:4px;animation:.5s linear infinite styles-module__jump7456Square___Z-D9b}.styles-module__circle___bUpSN:after{border-radius:50%;animation:.5s linear infinite styles-module__jump7456Circle___2AfF7}@keyframes styles-module__jump7456Square___Z-D9b{15%{border-bottom-right-radius:3px}25%{transform:translateY(9px)rotate(22.5deg)}50%{border-bottom-right-radius:40px;transform:translateY(18px)scaleY(.9)rotate(45deg)}75%{transform:translateY(9px)rotate(67.5deg)}to{transform:translateY(0)rotate(90deg)}}@keyframes styles-module__jump7456Circle___2AfF7{15%{border-bottom-right-radius:50%}25%{transform:translateY(9px)rotate(22.5deg)}50%{border-bottom-right-radius:40px;transform:translateY(18px)scaleY(.9)rotate(45deg)}75%{transform:translateY(9px)rotate(67.5deg)}to{transform:translateY(0)rotate(90deg)}}@keyframes styles-module__shadow324___sutUe{0%,to{transform:scale(1)}50%{transform:scaleX(1.2)}}.style-module__waves___oR3u7{width:100%;height:100%;margin:0;padding:0;position:absolute;top:0;left:0;overflow:hidden}.style-module__waves___oR3u7:before{content:"";width:.5rem;height:.5rem;transform:translate3d(calc(var(--x) - 50%), calc(var(--y) - 50%), 0);will-change:transform;background:#160000;border-radius:50%;position:absolute;top:0;left:0}.style-module__wavesCanvas___zkhTC{width:100%;height:100%;display:block}.style-module__squaresCanvas___22kMc{border:none;width:100%;height:100%;display:block}.style-module__container___HkxIe{background-color:#000;width:100%;height:100%;position:relative;overflow:hidden}.style-module__canvas___MR9JX{width:100%;height:100%;display:block}.style-module__outerVignette___i75nw{pointer-events:none;background:radial-gradient(circle,#0000 60%,#000 100%);width:100%;height:100%;position:absolute;top:0;left:0}.style-module__centerVignette___n9uVG{pointer-events:none;background:radial-gradient(circle,#000c 0%,#0000 60%);width:100%;height:100%;position:absolute;top:0;left:0}.styles-module__button___RbliA{cursor:pointer;border:1px solid #0000;border-radius:.5rem;outline:none;justify-content:center;align-items:center;gap:.5rem;font-family:inherit;font-weight:500;transition:all .2s;display:inline-flex;position:relative}.styles-module__button___RbliA:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.styles-module__button___RbliA:disabled{cursor:not-allowed;opacity:.6}.styles-module__button___RbliA.styles-module__small___yK0Yp{min-height:2rem;padding:.5rem 1rem;font-size:.8125rem}.styles-module__button___RbliA.styles-module__medium___eBTch{min-height:2.5rem;padding:.75rem 1.5rem;font-size:.875rem}.styles-module__button___RbliA.styles-module__large___pIZkl{min-height:3rem;padding:1rem 2rem;font-size:1rem}.styles-module__button___RbliA.styles-module__primary___qsZpA{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-primary-fg,#fff)}.styles-module__button___RbliA.styles-module__primary___qsZpA:hover:not(:disabled){background:var(--color-primary-light);border-color:var(--color-primary-light);transform:translateY(-1px)}.styles-module__button___RbliA.styles-module__primary___qsZpA:active:not(:disabled){transform:translateY(0)}.styles-module__button___RbliA.styles-module__secondary___SCpPv{background:var(--color-bg-2);border-color:var(--color-border-4);color:var(--color-fg-text)}.styles-module__button___RbliA.styles-module__secondary___SCpPv:hover:not(:disabled){background:var(--color-bg-3);border-color:var(--color-primary)}.styles-module__button___RbliA.styles-module__outline___rrlk9{border-color:var(--color-border-4);color:var(--color-fg-text);background:0 0}.styles-module__button___RbliA.styles-module__outline___rrlk9:hover:not(:disabled){background:var(--color-bg-2);border-color:var(--color-primary)}.styles-module__button___RbliA.styles-module__ghost___QMoiH{color:var(--color-fg-text);background:0 0;border-color:#0000}.styles-module__button___RbliA.styles-module__ghost___QMoiH:hover:not(:disabled){background:var(--color-bg-2)}.styles-module__button___RbliA.styles-module__danger___MW-pg{background:var(--color-danger);border-color:var(--color-danger);color:#fff}.styles-module__button___RbliA.styles-module__danger___MW-pg:hover:not(:disabled){background:var(--color-danger-light);border-color:var(--color-danger-light);transform:translateY(-1px)}.styles-module__button___RbliA.styles-module__danger___MW-pg:active:not(:disabled){transform:translateY(0)}.styles-module__button___RbliA.styles-module__fullWidth___-dXBR{width:100%}.styles-module__button___RbliA.styles-module__loading___UWw6V{pointer-events:none}.styles-module__spinner___KpJ-L{border:2px solid;border-top-color:#0000;border-radius:50%;flex-shrink:0;width:1rem;height:1rem;animation:.6s linear infinite styles-module__spin___ntZKM}@keyframes styles-module__spin___ntZKM{to{transform:rotate(360deg)}}.styles-module__button___RbliA.styles-module__small___yK0Yp .styles-module__spinner___KpJ-L{border-width:1.5px;width:.875rem;height:.875rem}.styles-module__button___RbliA.styles-module__large___pIZkl .styles-module__spinner___KpJ-L{border-width:2.5px;width:1.25rem;height:1.25rem}.styles-module__content___oyZRO{align-items:center;display:flex}.styles-module__leftIcon___cDktv,.styles-module__rightIcon___1mpk0{flex-shrink:0;align-items:center;display:flex}.styles-module__leftIcon___cDktv{margin-right:-.25rem}.styles-module__rightIcon___1mpk0{margin-left:-.25rem}.styles-module__button___RbliA.styles-module__iconOnly___iOv-U{aspect-ratio:1;min-width:2.5rem;min-height:2.5rem;padding:0}.styles-module__button___RbliA.styles-module__iconOnly___iOv-U.styles-module__small___yK0Yp{min-width:2rem;min-height:2rem}.styles-module__button___RbliA.styles-module__iconOnly___iOv-U.styles-module__large___pIZkl{min-width:3rem;min-height:3rem}.styles-module__iconContainer___bwvE3{justify-content:center;align-items:center;width:100%;height:100%;display:flex}.styles-module__layout___WaGy9{flex-direction:column;justify-content:center;align-items:center;gap:.25rem;width:100%;display:flex}.styles-module__horizontal___lKpGq{justify-content:center;align-items:center;gap:.5rem;width:100%;display:flex}.styles-module__topIcon___bzw6s,.styles-module__bottomIcon___suPfa{flex-shrink:0;justify-content:center;align-items:center;display:flex}.styles-module__iconOnly___iOv-U .styles-module__iconContainer___bwvE3 .styles-module__leftIcon___cDktv,.styles-module__iconOnly___iOv-U .styles-module__iconContainer___bwvE3 .styles-module__rightIcon___1mpk0{margin:0}.Dropdown-module__dropdown___c9wIp{width:100%;display:inline-block;position:relative}.Dropdown-module__dropdownButton___suNh-{border:1px solid var(--color-separator);cursor:pointer;text-align:left;background:var(--color-bg-2);width:100%;color:var(--color-fg-text);border-radius:8px;outline:none;justify-content:space-between;align-items:center;padding:.75rem 2.5rem .75rem 1rem;font-family:inherit;font-size:1rem;font-weight:400;line-height:1.5rem;transition:all .2s ease-in-out;display:flex;position:relative}.Dropdown-module__dropdownButton___suNh-:hover:not(:disabled){border-color:var(--color-primary);background:var(--color-bg-1)}.Dropdown-module__dropdownButton___suNh-:focus{border-color:var(--color-primary);background:var(--color-bg-1);box-shadow:0 0 0 2px var(--color-primary-transparent)}.Dropdown-module__dropdownButton___suNh-.Dropdown-module__disabled___Rz0pX{cursor:not-allowed;opacity:.6;background:var(--color-bg-3)}.Dropdown-module__dropdownButton___suNh-.Dropdown-module__error___F7dDu{border-color:#ef4444}.Dropdown-module__buttonText___D2zxn{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.Dropdown-module__chevronIcon___uyawp{color:var(--color-fg-highlight);flex-shrink:0;transition:transform .15s;position:absolute;right:1rem}.Dropdown-module__chevronIcon___uyawp.Dropdown-module__open___APoXd{transform:rotate(180deg)}.Dropdown-module__dropdownMenu___WJ-QO{background:var(--color-bg);border:1px solid var(--color-border-4);z-index:1000;border-radius:8px;min-width:100%;animation:.15s ease-out Dropdown-module__slideDown___sRCmO;position:absolute;top:calc(100% + .5rem);left:0;right:0;overflow:hidden;box-shadow:0 .5rem 1rem #0000002d}@keyframes Dropdown-module__slideDown___sRCmO{0%{opacity:0;transform:translateY(-.5rem)}to{opacity:1;transform:translateY(0)}}.Dropdown-module__optionsList___Be6RN{margin:0;padding:.5rem 0;list-style:none}.Dropdown-module__option___q-NgT{cursor:pointer;color:var(--color-fg-text);align-items:center;padding:.75rem 1rem;font-size:.875rem;transition:all .15s;display:flex}.Dropdown-module__option___q-NgT:hover{background-color:var(--color-bg-2)}.Dropdown-module__option___q-NgT.Dropdown-module__selected___-OTW-{background-color:var(--color-bg-3);color:var(--color-fg-highlight);font-weight:600}@media (width<=768px){.Dropdown-module__dropdownButton___suNh-{padding:.625rem 2.25rem .625rem .875rem;font-size:.875rem}.Dropdown-module__chevronIcon___uyawp{right:.875rem}.Dropdown-module__option___q-NgT{padding:.625rem .875rem;font-size:.8125rem}}.styles-module__wrapper___COeGm{flex-direction:column;gap:.5rem;display:flex}.styles-module__wrapper___COeGm.styles-module__fullWidth___lVU49{width:100%}.styles-module__label___gfOA7{color:var(--color-fg-heading);align-items:center;gap:.25rem;font-size:.875rem;font-weight:500;display:flex}.styles-module__required___-zOy5{color:var(--color-danger)}.styles-module__inputContainer___wXY53{align-items:center;width:100%;display:flex;position:relative}.styles-module__input___IZDc3{border:1px solid var(--color-border-4);background:var(--color-bg);width:100%;color:var(--color-fg-text);border-radius:.5rem;outline:none;font-family:inherit;font-size:.875rem;transition:all .2s}.styles-module__input___IZDc3[type=password]::-webkit-credentials-auto-fill-button{visibility:hidden!important;opacity:0!important;pointer-events:none!important;appearance:none!important;display:none!important;position:absolute!important;left:-9999px!important}.styles-module__input___IZDc3[type=password]::-webkit-strong-password-toggle-button{visibility:hidden!important;opacity:0!important;pointer-events:none!important;appearance:none!important;width:0!important;height:0!important;margin:0!important;padding:0!important;display:none!important;position:absolute!important;left:-9999px!important}.styles-module__input___IZDc3[type=password]::-ms-reveal{visibility:hidden!important;opacity:0!important;pointer-events:none!important;width:0!important;height:0!important;display:none!important;position:absolute!important;left:-9999px!important}.styles-module__input___IZDc3[type=password]::-ms-clear{visibility:hidden!important;opacity:0!important;pointer-events:none!important;width:0!important;height:0!important;display:none!important;position:absolute!important;left:-9999px!important}.styles-module__input___IZDc3:focus{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb,102, 126, 234), .1)}.styles-module__input___IZDc3::placeholder{color:var(--color-fg-muted,var(--color-fg-text));opacity:.5}.styles-module__input___IZDc3.styles-module__small___cuGyE{padding:.5rem .75rem;font-size:.8125rem}.styles-module__input___IZDc3.styles-module__medium___JdDPu{padding:.75rem;font-size:.875rem}.styles-module__input___IZDc3.styles-module__large___fMmiG{padding:1rem;font-size:1rem}.styles-module__input___IZDc3.styles-module__default___-eZTG{background:var(--color-bg);border-color:var(--color-border-4)}.styles-module__input___IZDc3.styles-module__filled___EQ5PQ{background:var(--color-bg-2);border-color:var(--color-border-3)}.styles-module__inputContainer___wXY53.styles-module__withLeftIcon___NFn6- .styles-module__input___IZDc3{padding-left:2.5rem}.styles-module__inputContainer___wXY53.styles-module__withRightIcon___mz-hn .styles-module__input___IZDc3{padding-right:2.5rem}.styles-module__inputContainer___wXY53.styles-module__withLeftIcon___NFn6- .styles-module__input___IZDc3.styles-module__small___cuGyE{padding-left:2rem}.styles-module__inputContainer___wXY53.styles-module__withRightIcon___mz-hn .styles-module__input___IZDc3.styles-module__small___cuGyE{padding-right:2rem}.styles-module__inputContainer___wXY53.styles-module__withLeftIcon___NFn6- .styles-module__input___IZDc3.styles-module__large___fMmiG{padding-left:3rem}.styles-module__inputContainer___wXY53.styles-module__withRightIcon___mz-hn .styles-module__input___IZDc3.styles-module__large___fMmiG{padding-right:3rem}.styles-module__leftIcon___jEW5H,.styles-module__rightIcon___bA3Eo{color:var(--color-fg-muted,var(--color-fg-text));pointer-events:none;z-index:1;justify-content:center;align-items:center;display:flex;position:absolute}.styles-module__leftIcon___jEW5H{left:.75rem}.styles-module__rightIcon___bA3Eo{right:.75rem}.styles-module__rightIcon___bA3Eo.styles-module__rightIconInteractive___ccypw{pointer-events:auto}.styles-module__inputContainer___wXY53.styles-module__containerSmall___20ICq .styles-module__leftIcon___jEW5H{left:.5rem}.styles-module__inputContainer___wXY53.styles-module__containerSmall___20ICq .styles-module__rightIcon___bA3Eo{right:.5rem}.styles-module__inputContainer___wXY53.styles-module__containerLarge___IztuN .styles-module__leftIcon___jEW5H{left:1rem}.styles-module__inputContainer___wXY53.styles-module__containerLarge___IztuN .styles-module__rightIcon___bA3Eo{right:1rem}.styles-module__input___IZDc3.styles-module__error___946qV{border-color:var(--color-danger)}.styles-module__input___IZDc3.styles-module__error___946qV:focus{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb,244, 67, 54), .1)}.styles-module__input___IZDc3.styles-module__disabled___sJvLd{background:var(--color-bg-3);border-color:var(--color-border-5);color:var(--color-fg-muted);cursor:not-allowed;opacity:.6}.styles-module__errorMessage___5Mn22{color:var(--color-danger);margin:0;font-size:.75rem}.styles-module__helperText___T2eui{color:var(--color-fg-muted);margin:0;font-size:.75rem}.styles-module__container___xeg8O{flex-direction:column;gap:1rem;display:flex}.styles-module__label___qhbKe{color:var(--color-fg-text);font-size:.875rem;font-weight:500}.styles-module__error___O-7AY{color:var(--color-error);font-size:.875rem}.styles-module__pairs___owGV3{flex-direction:column;gap:.75rem;display:flex}.styles-module__pair___G4WFY{grid-template-columns:1fr 2fr auto;align-items:start;gap:.75rem;display:grid}.styles-module__keyInput___wl1M1,.styles-module__valueInput___HiwJ5{flex:1}.styles-module__removeButton___4-X0b{min-width:auto;color:var(--color-error);padding:.5rem}.styles-module__removeButton___4-X0b:hover{background-color:var(--color-error);color:#fff}.styles-module__addButton___QFljt{align-self:flex-start}@media (width<=768px){.styles-module__pair___G4WFY{grid-template-columns:1fr}.styles-module__removeButton___4-X0b{align-self:flex-end}}.styles-module__searchContainer___86TGN{width:100%;max-width:400px;position:relative}.styles-module__searchIcon___ZInry{color:var(--color-fg-muted);pointer-events:none;position:absolute;top:50%;left:1rem;transform:translateY(-50%)}.styles-module__searchInput___SqZXQ{border:1px solid var(--color-border);width:100%;color:var(--color-fg-text);background:var(--color-bg-2);border-radius:.5rem;padding:.75rem 3rem;font-size:1rem;transition:all .2s}.styles-module__searchInput___SqZXQ:focus{border-color:var(--color-primary);box-shadow:0 0 0 3px var(--color-primary-bg);outline:none}.styles-module__searchInput___SqZXQ::placeholder{color:var(--color-fg-muted)}.styles-module__clearBtn___7jExr{width:1.5rem;height:1.5rem;color:var(--color-fg-muted);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:0;transition:all .2s;display:flex;position:absolute;top:50%;right:.75rem;transform:translateY(-50%)}.styles-module__clearBtn___7jExr:hover{color:var(--color-fg-text)}@media (width<=768px){.styles-module__searchContainer___86TGN{max-width:100%}}.styles-module__container___nGxdM{flex-direction:column;gap:.75rem;display:flex}.styles-module__toggleContainer___VrsKI{align-items:center;display:flex}.styles-module__toggleButton___IzD6Z{border:1px solid var(--color-border-4);background:var(--color-bg);color:var(--color-fg-muted);cursor:pointer;border-radius:.5rem;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.875rem;transition:all .2s;display:flex}.styles-module__toggleButton___IzD6Z:hover{background:var(--color-bg-2);border-color:var(--color-border-3)}.styles-module__toggleButton___IzD6Z.styles-module__enabled___cao0G{background:var(--color-primary);color:#fff;border-color:var(--color-primary)}.styles-module__toggleButton___IzD6Z.styles-module__enabled___cao0G:hover{background:var(--color-primary-hover);border-color:var(--color-primary-hover)}.styles-module__optionsContainer___LOzlO{background:var(--color-bg-2);border:1px solid var(--color-border-4);border-radius:.5rem;gap:.5rem;padding:.5rem;display:flex}.styles-module__option___abhnC{border:1px solid var(--color-border-4);background:var(--color-bg);color:var(--color-fg);cursor:pointer;border-radius:.375rem;flex:1;justify-content:center;align-items:center;gap:.375rem;padding:.5rem .875rem;font-size:.875rem;font-weight:500;transition:all .2s;display:flex}.styles-module__option___abhnC:hover{background:var(--color-bg-3);border-color:var(--color-border-3)}.styles-module__option___abhnC.styles-module__active___pqmSi{background:var(--color-primary);color:#fff;border-color:var(--color-primary)}.styles-module__option___abhnC.styles-module__active___pqmSi:hover{background:var(--color-primary-hover);border-color:var(--color-primary-hover)}.styles-module__nbSelect___KjxSc{font-family:inherit;font-size:.9375rem;font-weight:600;line-height:1.5rem;display:inline-block;position:relative}.styles-module__selectButton___AKZe4{cursor:pointer;text-align:left;width:100%;min-width:8rem;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;appearance:none;border:1px solid;border-radius:.25rem;outline:none;justify-content:space-between;align-items:center;padding:.4375rem 2.2rem .4375rem 1.125rem;transition:all .15s ease-in-out;display:flex;position:relative}.styles-module__selectButton___AKZe4.styles-module__primary___HU36a{background-color:var(--color-primary);border-color:var(--color-primary);color:var(--color-primary-fg)}.styles-module__selectButton___AKZe4.styles-module__primary___HU36a:hover:not(:disabled){background-color:var(--color-primary-light);border-color:var(--color-primary-light)}.styles-module__selectButton___AKZe4.styles-module__primary___HU36a:focus,.styles-module__selectButton___AKZe4.styles-module__primary___HU36a.styles-module__open___9AKMb{background-color:var(--color-primary-light);border-color:var(--color-primary-light);box-shadow:0 0 0 .125rem #8f9bb340}.styles-module__selectButton___AKZe4.styles-module__default___LedYP{background-color:var(--color-bg);border-color:var(--color-border-4);color:var(--color-fg-text)}.styles-module__selectButton___AKZe4.styles-module__default___LedYP:hover:not(:disabled){background-color:var(--color-bg-2);border-color:var(--color-primary)}.styles-module__selectButton___AKZe4.styles-module__default___LedYP:focus,.styles-module__selectButton___AKZe4.styles-module__default___LedYP.styles-module__open___9AKMb{background-color:var(--color-bg-2);border-color:var(--color-primary);box-shadow:0 0 0 .125rem #8f9bb340}.styles-module__selectButton___AKZe4:disabled{cursor:not-allowed;opacity:.6}.styles-module__buttonText___QSLO1{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.styles-module__chevronIcon___qAHaD{flex-shrink:0;width:1.5rem;height:1.5rem;transition:transform .15s;position:absolute;right:.41rem}.styles-module__selectButton___AKZe4.styles-module__primary___HU36a .styles-module__chevronIcon___qAHaD{color:var(--color-primary-fg)}.styles-module__selectButton___AKZe4.styles-module__default___LedYP .styles-module__chevronIcon___qAHaD{color:var(--color-fg)}.styles-module__chevronIcon___qAHaD.styles-module__open___9AKMb{transform:rotate(180deg)}.styles-module__optionsPanel___NWAp3{background-color:var(--color-bg);border:1px solid var(--color-border-4);z-index:1000;transform-origin:top;visibility:hidden;pointer-events:none;border-radius:.25rem;min-width:100%;position:absolute;top:calc(100% + .5rem);left:0;right:0;overflow:hidden;box-shadow:0 .5rem 1rem #00000026}.styles-module__optionsPanel___NWAp3.styles-module__open___9AKMb{visibility:visible;pointer-events:auto;animation:.25s ease-out styles-module__expandDown___eD4lh}.styles-module__optionsPanel___NWAp3.styles-module__closed___bez-q{visibility:hidden;pointer-events:none;animation:.25s ease-out styles-module__collapseUp___dt1E7}@keyframes styles-module__expandDown___eD4lh{0%{opacity:0;transform:scaleY(0)}to{opacity:1;transform:scaleY(1)}}@keyframes styles-module__collapseUp___dt1E7{0%{opacity:1;transform:scaleY(1)}to{opacity:0;transform:scaleY(0)}}.styles-module__optionsPanel___NWAp3.styles-module__primary___HU36a{border-color:var(--color-primary)}.styles-module__optionsPanel___NWAp3.styles-module__default___LedYP{border-color:var(--color-border-4)}.styles-module__optionsList___AxhVp{margin:0;padding:.5rem 0;list-style:none}.styles-module__option___t1SSw{cursor:pointer;color:var(--color-fg-text);justify-content:space-between;align-items:center;padding:.75rem 1.125rem;transition:all .15s;display:flex}.styles-module__option___t1SSw:hover{background-color:var(--color-bg-2)}.styles-module__option___t1SSw.styles-module__selected___azD4A{background-color:var(--color-primary);color:var(--color-primary-fg);font-weight:700}@media (prefers-color-scheme:dark){.styles-module__optionsPanel___NWAp3{box-shadow:0 .5rem 1rem #0000004d}}.styles-module__sliderContainer___lgpaD{flex-direction:column;gap:.5rem;display:flex}.styles-module__sliderContainer___lgpaD.styles-module__fullWidth___ZS8W5{width:100%}.styles-module__labelRow___dkVVM{justify-content:space-between;align-items:center;min-height:1.5rem;margin-bottom:.5rem;display:flex}.styles-module__label___bXGZs{color:var(--color-fg-text);font-size:.875rem;font-weight:500}.styles-module__required___FuyfE{color:var(--color-danger);margin-left:.25rem}.styles-module__sliderWrapper___1Wdti{touch-action:none;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;gap:.75rem;width:100%;display:flex}.styles-module__sliderRoot___yzOjR{cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none;flex-grow:1;align-items:center;width:100%;padding:.75rem 0;display:flex;position:relative}.styles-module__sliderRoot___yzOjR:active{cursor:grabbing}.styles-module__sliderTrackWrapper___N72WQ{flex-grow:1;display:flex;position:relative}.styles-module__sliderTrack___AltcD{background-color:var(--color-bg-3);border-radius:9999px;width:100%;height:.375rem;position:relative;overflow:hidden}.styles-module__sliderRange___U3g0-{background-color:var(--color-primary);border-radius:9999px;height:100%;position:absolute}.styles-module__valueIndicator___aLf0I{color:var(--color-primary);text-align:center;flex-shrink:0;min-width:2rem;font-size:.875rem;font-weight:600;line-height:1}.styles-module__iconWrapper___wL-b8{flex-shrink:0;justify-content:center;align-items:center;display:flex}.styles-module__icon___vHsID{width:20px;height:20px;color:var(--color-fg-muted)}.styles-module__errorMessage___qFgGe{color:var(--color-danger);margin:0;font-size:.75rem}.styles-module__helperText___tgBGl{color:var(--color-fg-muted);margin:0;font-size:.75rem}.styles-module__loadingContainer___OMgwS{flex-direction:column;justify-content:center;align-items:center;gap:1.5rem;min-height:60vh;display:flex}.styles-module__loadingText___KRZqR{color:var(--color-fg-muted);font-size:1rem}.styles-module__errorContainer___qhAg2{text-align:center;min-height:60vh;color:var(--color-fg-text);flex-direction:column;justify-content:center;align-items:center;gap:2rem;padding:3rem 2rem;display:flex}.styles-module__errorIconWrapper___IHZrN{background:linear-gradient(135deg, rgba(var(--color-error-rgb,239, 68, 68), .1), rgba(var(--color-error-rgb,239, 68, 68), .05));border:2px solid rgba(var(--color-error-rgb,239, 68, 68), .2);border-radius:50%;justify-content:center;align-items:center;width:120px;height:120px;animation:2s ease-in-out infinite styles-module__pulse___Lju-z;display:flex}@keyframes styles-module__pulse___Lju-z{0%,to{opacity:1;transform:scale(1)}50%{opacity:.9;transform:scale(1.05)}}.styles-module__errorIcon___oGDIP{color:var(--color-error,#ef4444);stroke-width:1.5px}.styles-module__errorContent___F55rQ{flex-direction:column;align-items:center;gap:1rem;width:100%;max-width:600px;display:flex}.styles-module__errorTitle___hC8D-{color:var(--color-fg-heading);letter-spacing:-.02em;margin:0;font-size:1.75rem;font-weight:700;line-height:1.2}.styles-module__errorDescription___hZtuP{color:var(--color-fg-muted);max-width:480px;margin:0;font-size:1rem;line-height:1.6}.styles-module__errorDetailsWrapper___-sp8b{width:100%;max-width:600px;margin-top:.5rem}.styles-module__errorDetailsContainer___eywMo{text-align:left;border:1px solid var(--color-border,#0000001a);background:var(--color-bg-2,#00000005);border-radius:.75rem;width:100%;transition:all .2s;overflow:hidden}.styles-module__errorDetailsContainer___eywMo:hover{border-color:var(--color-border-hover,#00000026);background:var(--color-bg-3,#00000008)}.styles-module__errorDetailsSummary___hGEoD{cursor:pointer;color:var(--color-fg-muted);-webkit-user-select:none;user-select:none;padding:.875rem 1.25rem;font-size:.875rem;font-weight:500;list-style:none;transition:color .2s}.styles-module__errorDetailsSummary___hGEoD:hover{color:var(--color-fg-text)}.styles-module__errorDetailsSummary___hGEoD::-webkit-details-marker{display:none}.styles-module__errorDetailsSummary___hGEoD:before{content:"▶";margin-right:.5rem;font-size:.75rem;transition:transform .2s;display:inline-block}.styles-module__errorDetailsContainer___eywMo[open] .styles-module__errorDetailsSummary___hGEoD:before{transform:rotate(90deg)}.styles-module__errorDetails___tzCT-{background:var(--color-bg-3,#00000008);border-top:1px solid var(--color-border,#0000001a);max-width:100%;color:var(--color-fg-muted);white-space:pre-wrap;word-break:break-word;margin:0;padding:1rem 1.25rem;font-family:Monaco,Menlo,Ubuntu Mono,Consolas,source-code-pro,monospace;font-size:.8125rem;line-height:1.6;overflow-x:auto}.styles-module__errorActions___gtqYw{flex-wrap:wrap;justify-content:center;align-items:center;gap:.75rem;margin-top:1rem;display:flex}.styles-module__retryButton___TA4jm{background:var(--color-primary);color:var(--color-primary-fg);cursor:pointer;border:none;border-radius:.5rem;justify-content:center;align-items:center;gap:.5rem;padding:.75rem 2rem;font-size:.9375rem;font-weight:500;transition:all .2s;display:inline-flex;box-shadow:0 2px 8px #00000014}.styles-module__retryButton___TA4jm:hover{background:var(--color-primary-hover,var(--color-primary));transform:translateY(-2px);box-shadow:0 4px 16px #0000001f}.styles-module__retryButton___TA4jm:active{transform:translateY(0);box-shadow:0 2px 8px #00000014}.styles-module__retryButtonIcon___vzfp-{font-size:1.125rem;line-height:1;transition:transform .3s;display:inline-block}.styles-module__retryButton___TA4jm:hover .styles-module__retryButtonIcon___vzfp-{transform:rotate(180deg)}.styles-module__clearLocalDataButton___T3B-K{color:var(--color-fg-muted);border:1px solid var(--color-border-4);cursor:pointer;background:0 0;border-radius:.5rem;justify-content:center;align-items:center;padding:.75rem 1.5rem;font-size:.9375rem;font-weight:500;transition:all .2s;display:inline-flex}.styles-module__clearLocalDataButton___T3B-K:hover{color:var(--color-fg-text);border-color:var(--color-border-3);background:var(--color-bg-secondary)}.styles-module__wrapper___jXPV3{flex-direction:column;gap:.5rem;display:flex}.styles-module__wrapper___jXPV3.styles-module__fullWidth___eKyGB{width:100%}.styles-module__label___MyTeo{color:var(--color-fg-heading);align-items:center;gap:.25rem;font-size:.875rem;font-weight:500;display:flex}.styles-module__required___TV-m8{color:var(--color-danger)}.styles-module__inputContainer___VvGBl{align-items:flex-start;width:100%;display:flex;position:relative}.styles-module__textarea___AjX9N{border:1px solid var(--color-border-4);background:var(--color-bg);width:100%;color:var(--color-fg-text);resize:vertical;border-radius:.5rem;outline:none;min-height:4rem;font-family:inherit;font-size:.875rem;transition:all .2s}.styles-module__textarea___AjX9N:focus{border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(var(--color-primary-rgb,102, 126, 234), .1)}.styles-module__textarea___AjX9N::placeholder{color:var(--color-fg-muted,var(--color-fg-text));opacity:.5}.styles-module__textarea___AjX9N.styles-module__small___zMx0G{min-height:3rem;padding:.5rem .75rem;font-size:.8125rem}.styles-module__textarea___AjX9N.styles-module__medium___tO2Lb{min-height:4rem;padding:.75rem;font-size:.875rem}.styles-module__textarea___AjX9N.styles-module__large___3eU2g{min-height:5rem;padding:1rem;font-size:1rem}.styles-module__textarea___AjX9N.styles-module__default___vsSxX{background:var(--color-bg);border-color:var(--color-border-4)}.styles-module__textarea___AjX9N.styles-module__filled___TPQgI{background:var(--color-bg-2);border-color:var(--color-border-3)}.styles-module__inputContainer___VvGBl .styles-module__leftIcon___FHp2R{color:var(--color-fg-muted,var(--color-fg-text));pointer-events:none;z-index:1;justify-content:center;align-items:center;display:flex;position:absolute;top:.75rem;left:.75rem}.styles-module__inputContainer___VvGBl .styles-module__rightIcon___dkyvP{color:var(--color-fg-muted,var(--color-fg-text));pointer-events:none;z-index:1;justify-content:center;align-items:center;display:flex;position:absolute;top:.75rem;right:.75rem}.styles-module__inputContainer___VvGBl .styles-module__textarea___AjX9N.styles-module__small___zMx0G+.styles-module__leftIcon___FHp2R,.styles-module__inputContainer___VvGBl .styles-module__textarea___AjX9N.styles-module__small___zMx0G+.styles-module__rightIcon___dkyvP{top:.5rem}.styles-module__inputContainer___VvGBl .styles-module__textarea___AjX9N.styles-module__large___3eU2g+.styles-module__leftIcon___FHp2R,.styles-module__inputContainer___VvGBl .styles-module__textarea___AjX9N.styles-module__large___3eU2g+.styles-module__rightIcon___dkyvP{top:1rem}.styles-module__textarea___AjX9N.styles-module__error___d8gLK{border-color:var(--color-danger)}.styles-module__textarea___AjX9N.styles-module__error___d8gLK:focus{border-color:var(--color-danger);box-shadow:0 0 0 3px rgba(var(--color-danger-rgb,244, 67, 54), .1)}.styles-module__textarea___AjX9N.styles-module__disabled___IzbfE{background:var(--color-bg-3);border-color:var(--color-border-5);color:var(--color-fg-muted);cursor:not-allowed;opacity:.6}.styles-module__errorText___Nom7x{color:var(--color-danger);margin:0;font-size:.75rem}.styles-module__helperText___97Def{color:var(--color-fg-muted);margin:0;font-size:.75rem}.styles-module__virtualList___pM6rN{contain:strict;width:100%;padding-bottom:300px;position:relative;overflow:auto}.styles-module__emptyContainer___rm938{height:100%;color:var(--color-fg-muted);justify-content:center;align-items:center;display:flex}.styles-module__virtualListInner___HxRzc{width:100%;position:relative}.styles-module__virtualListItems___p19-a{width:100%;position:absolute;top:0;left:0}.styles-module__loadingMore___zAdLM{color:var(--color-fg-muted);justify-content:center;align-items:center;gap:.75rem;padding:2rem 0;font-size:.875rem;display:flex}.styles-module__endOfList___5cYFh{color:var(--color-fg-muted);border-top:1px solid var(--color-border-3);justify-content:center;margin-top:1rem;padding:2rem 0;font-size:.875rem;display:flex}.styles-module__endOfList___5cYFh span{background-color:var(--color-bg-2);border-radius:.25rem;padding:.5rem 1rem}.styles-module__virtualList___xmj2v{width:100%;padding-bottom:300px;position:relative;overflow:visible}.styles-module__virtualListInner___quD74{width:100%;position:relative}.styles-module__virtualListItems___-Muml{width:100%;position:absolute;top:0;left:0}.styles-module__virtualListRow___h3pzX{width:100%}.styles-module__emptyContainer___LIy8r{color:var(--color-fg-muted);justify-content:center;align-items:center;padding:2rem 0;display:flex}.styles-module__loadingMore___Mzf-p{color:var(--color-fg-muted);justify-content:center;align-items:center;padding:1.5rem 0;display:flex}.styles-module__endOfList___g5G--{color:var(--color-fg-muted);border-top:1px solid var(--color-border-3);justify-content:center;padding:1.5rem 0;display:flex}.styles-module__sidebar___BMt0j{flex-direction:column;height:100%;min-height:0;display:flex;overflow:hidden}.styles-module__sidebarContent___iAwiw{flex-direction:column;height:100%;min-height:0;padding-top:1rem;display:flex;overflow:hidden}.styles-module__menuWrapper___DKuEv{flex:1;min-height:0;overflow:hidden auto}.styles-module__sidebarContent___iAwiw::-webkit-scrollbar{width:0}.styles-module__sidebarContent___iAwiw::-webkit-scrollbar-track{background:0 0}.styles-module__sidebarContent___iAwiw::-webkit-scrollbar-thumb{background:0 0}.styles-module__sidebarContent___iAwiw::-webkit-scrollbar-thumb:hover{background:0 0}.styles-module__sidebar___BMt0j a{margin-left:0}.styles-module__sidebar___BMt0j .ps-sidebar-root{background-color:var(--color-bg-2)!important;border-right:1px solid var(--color-separator)!important;flex-direction:column!important;height:100%!important;min-height:0!important;display:flex!important;overflow:hidden!important}.styles-module__sidebar___BMt0j .ps-sidebar-container{background-color:var(--color-bg-2)!important;height:100%!important;min-height:0!important;color:var(--color-fg-text)!important;flex-direction:column!important;flex:1!important;display:flex!important;overflow:hidden!important}.styles-module__sidebar___BMt0j .ps-menu-button{color:var(--color-fg-text)!important;border-radius:.375rem!important;margin:.125rem .5rem!important;padding:.5rem .75rem!important;font-size:.875rem!important;transition:all .2s!important}.styles-module__sidebar___BMt0j .ps-menu-button:hover{background-color:var(--color-bg-3)!important;color:var(--color-fg-text)!important}.styles-module__sidebar___BMt0j .ps-menu-button.ps-active{background-color:var(--color-primary)!important;color:#fff!important}.styles-module__sidebar___BMt0j .ps-menu-button.ps-active *,.styles-module__sidebar___BMt0j .ps-menu-button.ps-active .ps-menu-icon,.styles-module__sidebar___BMt0j .ps-menu-button.ps-active .ps-menu-label{color:#fff!important}.styles-module__sidebar___BMt0j .ps-menu-icon{width:1.25rem!important;height:1.25rem!important;color:var(--color-fg-text)!important;margin-right:.5rem!important}.styles-module__sidebar___BMt0j .ps-submenu-content{background-color:var(--color-bg-3)!important;padding-left:.5rem!important;overflow:hidden!important}.styles-module__sidebar___BMt0j .ps-submenu-content .ps-menu-button{margin:.125rem .25rem!important;padding:.375rem .5rem!important;font-size:.8125rem!important;transition:all .2s!important}.styles-module__sidebar___BMt0j .ps-menu-label{color:var(--color-fg-text)!important;font-weight:500!important}.styles-module__sidebar___BMt0j .ps-menu-button.ps-active,.styles-module__sidebar___BMt0j .ps-menu-button.ps-active span,.styles-module__sidebar___BMt0j .ps-menu-button.ps-active div,.styles-module__sidebar___BMt0j .ps-menu-button.ps-active .ps-menu-icon,.styles-module__sidebar___BMt0j .ps-menu-button.ps-active .ps-menu-label{color:#fff!important}.styles-module__logoutContainer___7NwBL{border-top:1px solid var(--color-separator);writing-mode:horizontal-tb;text-orientation:mixed;flex-shrink:0;margin-top:auto;padding:.5rem}.styles-module__logoutButton___rfD6u{width:100%;color:var(--color-fg-text);cursor:pointer;white-space:nowrap;writing-mode:horizontal-tb;text-orientation:mixed;direction:ltr;background:0 0;border:none;border-radius:.375rem;justify-content:flex-start;align-items:center;gap:.5rem;padding:.5rem .75rem;font-size:.875rem;transition:background-color .2s,color .2s;display:flex;overflow:hidden}.styles-module__logoutButton___rfD6u span{opacity:1;max-width:200px;transition:opacity .3s,max-width .3s;display:inline-block;writing-mode:horizontal-tb!important;text-orientation:mixed!important;direction:ltr!important;transform:none!important}.styles-module__logoutButton___rfD6u .styles-module__hiddenText___z183T{opacity:0;width:0;max-width:0;overflow:hidden}.styles-module__logoutButton___rfD6u .styles-module__visibleText___lfsv-{opacity:1;width:auto;max-width:200px}.styles-module__logoutButton___rfD6u:hover{background-color:var(--color-bg-3)}.styles-module__logoutButton___rfD6u:active{transform:scale(.98)}.styles-module__logoutButton___rfD6u svg{color:inherit;flex-shrink:0}.styles-module__footer___g-hY7{background:var(--color-bg-2);width:100%;padding:0 2rem}.styles-module__footerContent___Oen5n{justify-content:space-between;align-items:center;max-width:100%;margin:0 auto;display:flex}.styles-module__copyright___1R2o3{color:var(--color-fg-text);font-size:.875rem}.styles-module__links___dQyvB{gap:1.5rem;display:flex}.styles-module__link___UWfyK{color:var(--color-fg-text);font-size:.875rem;text-decoration:none;transition:color .3s}.styles-module__link___UWfyK:hover{color:var(--color-primary)}@media (width<=768px){.styles-module__footerContent___Oen5n{flex-direction:column;gap:1rem}.styles-module__links___dQyvB{gap:1rem}}.styles-module__header___4Uc8h{width:100%;padding:.5rem 2rem;display:flex}.styles-module__header___4Uc8h>:first-child{flex:1;justify-content:flex-start;align-items:center;display:flex}.styles-module__header___4Uc8h>:last-child{flex:1;justify-content:flex-end;align-items:center;display:flex}.styles-module__layout___ZQKU7{background:var(--color-bg);flex-direction:column;width:100%;height:100vh;display:flex;position:relative}.styles-module__header___XIaFb{z-index:100;background:var(--color-bg);width:100%;position:fixed;top:0;box-shadow:0 .125rem .25rem #2c33491a}.styles-module__footer___r8ASO{z-index:100;width:100%;position:fixed;bottom:0}.styles-module__mainWrapper___i4tEI{flex:1;display:flex;position:relative}.styles-module__sidebar___ij-8-{position:fixed;top:0}.styles-module__content___mTgUr{background:var(--color-bg);flex:1;min-height:calc(100vh - 10rem);margin:0;padding-top:0;padding-bottom:0;overflow:hidden auto}@media (width<=768px){.styles-module__content___mTgUr{width:100%}.styles-module__sidebar___ij-8-{z-index:999;height:100vh;position:fixed;top:0;left:0}}.styles-module__mainWrapper___AiTAw{flex-direction:row;flex:1;display:flex;position:relative}.styles-module__sidebarContainer___X4yj8{z-index:99;flex-direction:column;display:flex;position:fixed;left:0;overflow:hidden}.styles-module__sidebar___VQTlH{flex-shrink:0;top:0;left:0}.styles-module__content___htJlH{background:var(--color-bg);flex:1;margin:0;padding-top:0;padding-bottom:0;overflow:hidden auto}@media (width<=1024px){.styles-module__content___htJlH{padding:1.5rem}}@media (width<=768px){.styles-module__content___htJlH{width:100%}.styles-module__sidebar___VQTlH{z-index:999;height:100vh;position:fixed;top:0;left:0}}.styles-module__wavesWrapper___TK7DI{pointer-events:none;z-index:0;width:100vw;height:100vh;position:fixed;inset:0;overflow:hidden}.styles-module__wavesWrapper___TK7DI>*{pointer-events:auto}.styles-module__squaresWrapper___P25uF{pointer-events:none;z-index:0;width:100vw;height:100vh;position:fixed;inset:0;overflow:hidden}.styles-module__squaresWrapper___P25uF>*{pointer-events:auto}.styles-module__letterGlitchWrapper___KWtWU{pointer-events:none;z-index:0;width:100vw;height:100vh;position:fixed;inset:0;overflow:hidden}.styles-module__letterGlitchWrapper___KWtWU>*{pointer-events:auto}.styles-module__pixelBlastWrapper___YptHh{pointer-events:none;z-index:0;width:100vw;height:100vh;position:fixed;inset:0;overflow:hidden}.styles-module__pixelBlastWrapper___YptHh>*{pointer-events:auto}.style-module__container___Hh3-c{width:100%;height:100%;position:relative;overflow:hidden}
|
|
2
2
|
/*$vite$:1*/`)),document.head.appendChild(e)}}catch(o){console.error("vite-plugin-css-injected-by-js",o)}})();
|
|
3
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
3
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const I=require("./chunk-chunk-BFrxaqQT.cjs"),p=require("./chunk-lstorage-BnxLXHgH.cjs"),N=require("./chunk-i18n-BCDF-skF.cjs");let m=require("async-retry");m=I.__toESM(m);function A(t){return t.trim().replace(/\s+/g," ")}function y(t){const r=t?.response?.data;return!r||typeof r!="object"?null:{status:r.status,errCode:r.errCode,message:r.message,details:r.details,traceId:r.traceId}}function D(t,r){const e=y(t);if(e?.errCode){const n=N.i18n_default.t(`errors:${e.errCode}`);if(n&&n!==`errors:${e.errCode}`)return n}return e?.message?e.message:r}function T(t,r,e,n,o){if(r.length===0)return t;switch(o){case"insert":return E(t,r,e,n);case"upsert":return k(t,r,e,n)}}function E(t,r,e,n){const o=new Set(t.map(e)),i=r.filter(a=>{const s=e(a);return o.has(s)?!1:(o.add(s),!0)});return i.length===0?t:n==="prepend"?[...i,...t]:[...t,...i]}function k(t,r,e,n){const o=new Map;t.forEach((u,f)=>o.set(e(u),f));let i=!1;const a=t.slice();for(const u of r){const f=e(u),c=o.get(f);c!==void 0&&a[c]!==u&&(a[c]=u,i=!0)}const s=[];for(const u of r){const f=e(u);o.has(f)||s.push(u)}return s.length>0?(i=!0,n==="prepend"?[...s,...a]:[...a,...s]):i?a:t}function R(t,r,e){let n=!1;const o=t.filter(i=>{const a=!r.has(e(i));return a||(n=!0),a});return n?o:t}var v=(t,r)=>{const e=t.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);if(e){const[,n,o,i]=e;return`rgba(${n}, ${o}, ${i}, ${r})`}return h(t,r)},B=(t,r)=>{if(t.startsWith("rgba"))return t;const e=t.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);if(e){const[,n,o,i]=e;return`rgba(${n}, ${o}, ${i}, ${r})`}return console.warn("Invalid RGB color format:",t),t},h=(t,r)=>{if(t.indexOf("rgb")!==-1)return t;let e=0,n=0,o=0;return t.startsWith("#")&&(t=t.slice(1)),t.length===3?(e=parseInt(t[0]+t[0],16),n=parseInt(t[1]+t[1],16),o=parseInt(t[2]+t[2],16)):t.length===6?(e=parseInt(t.slice(0,2),16),n=parseInt(t.slice(2,4),16),o=parseInt(t.slice(4,6),16)):console.warn("Unsupported HEX color format"),`rgba(${e}, ${n}, ${o}, ${r})`},C=(t,r,e)=>{const n=c=>{const l=c.match(/^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i);if(l)return[parseInt(l[1],16),parseInt(l[2],16),parseInt(l[3],16)];const d=c.match(/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/i);return d?[parseInt(d[1]),parseInt(d[2]),parseInt(d[3])]:[0,0,0]},[o,i,a]=n(t),[s,u,f]=n(r);return`rgb(${Math.round(o+(s-o)*e)}, ${Math.round(i+(u-i)*e)}, ${Math.round(a+(f-a)*e)})`},P=t=>t[Math.floor(Math.random()*t.length)]||"#ffffff",q=t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t);function _(t){return typeof t=="string"?t:t==null?"":typeof t=="number"||typeof t=="boolean"?String(t):Array.isArray(t)?t.map(r=>String(r)).join(", "):typeof t=="object"?JSON.stringify(t):String(t)}function j(t){return t==null?"":typeof t=="number"?Number.isFinite(t)?String(t):"":typeof t=="string"?t:typeof t=="boolean"?t?"1":"0":Array.isArray(t)&&t.length>0?String(t[0]):""}function F(t,r,...e){const n={},o=Array.isArray(r)?r:[r,...e],i=new Set(o);for(const a of Object.keys(t))i.has(a)||(n[a]=t[a]);return n}var $=t=>[t,null],b=t=>[null,t instanceof Error?t:new Error(String(t))];async function M(t,r){try{return $(await(0,m.default)(async(e,n)=>{try{return await t(e,n)}catch(o){throw r?.isNonRetryable?.(o)&&e(o),o}},r))}catch(e){return b(e)}}async function G(t,r,e){return M(async(n,o)=>{const i=await t();if(!r(i))throw new Error("Condition not satisfied");return i},e)}function V(t){return Math.round(Number(t)*100)}function w(t){return(Number(t)/100).toFixed(2)}function H(t){return Number(w(t))}function O(t){return t<1e3?t.toString():t<1e6?`${(t/1e3).toFixed(1).replace(/\.0$/,"")}k+`:t<1e9?`${(t/1e6).toFixed(1).replace(/\.0$/,"")}M+`:`${(t/1e9).toFixed(1).replace(/\.0$/,"")}B+`}function U(t){let r=null;return async(...e)=>r||(r=t(...e).finally(()=>r=null),r)}function W(t,r){const e=new Map;return async(...n)=>{const o=r(...n),i=e.get(o);if(i)return i;const a=t(...n).finally(()=>e.delete(o));return e.set(o,a),a}}function Y(t){return t===void 0?null:t}function K(t){return t??void 0}function z(t){return t===""?void 0:t}function J(t,r){return t??r??[]}function Z(t){return t??0}function x(t){return t==null||t===""?"":t}function L(t,r){return t??r}function X(t){if(t==null)return;const r=Number(t);return Number.isNaN(r)?void 0:r}function g(t,r){if(Object.is(t,r))return!0;if(typeof t!=typeof r)return!1;if(t===null||r===null||typeof t!="object"||typeof r!="object")return t===r;const e=Array.isArray(t),n=Array.isArray(r);if(e!==n)return!1;if(e&&n)return t.length!==r.length?!1:t.every((a,s)=>g(a,r[s]));const o=Object.keys(t).sort(),i=Object.keys(r).sort();return o.length!==i.length||!o.every((a,s)=>a===i[s])?!1:o.every(a=>g(t[a],r[a]))}var Q=t=>{let r,e=[];return new Proxy(t,{construct(n,o){if(r||(r=new t(...o),e=o),!g(o,e))throw new Error("Cannot create multiple instances with different parameters");return r}})};function tt(t,r=100){if(t==null)throw new Promise(e=>setTimeout(e,r));return t}var rt=t=>{if(!t)return"";const r=new Date(t);return isNaN(r.getTime())?"":`${r.getFullYear()}/${(r.getMonth()+1).toString().padStart(2,"0")}/${r.getDate().toString().padStart(2,"0")}`},et=t=>{if(!t)return"Unknown";const r=new Date(t);if(isNaN(r.getTime()))return"Invalid date";const e=new Date().getTime()-r.getTime(),n=Math.floor(e/1e3),o=Math.floor(n/60),i=Math.floor(o/60),a=Math.floor(i/24),s=Math.floor(a/30),u=Math.floor(a/365);return u>0?`${u} year${u>1?"s":""} ago`:s>0?`${s} month${s>1?"s":""} ago`:a>0?`${a} day${a>1?"s":""} ago`:i>0?`${i} hour${i>1?"s":""} ago`:o>0?`${o} minute${o>1?"s":""} ago`:"Just now"};function nt(t){try{return new Date(t).toLocaleDateString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}catch{return t}}var ot=t=>{try{const r=new Date(t);return isNaN(r.getTime())?t:`${r.getFullYear()}/${String(r.getMonth()+1).padStart(2,"0")}/${String(r.getDate()).padStart(2,"0")} ${String(r.getHours()).padStart(2,"0")}:${String(r.getMinutes()).padStart(2,"0")}`}catch{return t}},at=t=>{try{const r=new Date(t);return isNaN(r.getTime())?t:`${String(r.getMonth()+1).padStart(2,"0")}/${String(r.getDate()).padStart(2,"0")} ${String(r.getHours()).padStart(2,"0")}:${String(r.getMinutes()).padStart(2,"0")}`}catch{return t}},it=t=>{try{const r=new Date(t);return isNaN(r.getTime())?t:`${r.getFullYear()}/${String(r.getMonth()+1).padStart(2,"0")}/${String(r.getDate()).padStart(2,"0")}`}catch{return t}},st=t=>{try{const r=new Date(t);return isNaN(r.getTime())?t:`${String(r.getHours()).padStart(2,"0")}:${String(r.getMinutes()).padStart(2,"0")}`}catch{return t}},ut=(t,r)=>t+Math.random()*(r-t),ft=(t,r)=>{if(r<t)throw new Error("randomInt: maxInclusive must be >= minInclusive");const e=r-t+1;return t+Math.floor(Math.random()*e)},S=(t=.5)=>{if(t<0||t>1)throw new Error("randomBool: probability must be within [0, 1]");return Math.random()<t},ct=(t=.5)=>S(t),lt=()=>Math.random()<.5?-1:1,dt=t=>{if(t.length===0)throw new Error("pickRandom: empty pool");return t[Math.floor(Math.random()*t.length)]},mt=(t=0,r=1)=>{if(r<=0)throw new Error("randomGaussian: stdDev must be > 0");let e=0,n=0;for(;e===0;)e=Math.random();for(;n===0;)n=Math.random();return Math.sqrt(-2*Math.log(e))*Math.cos(2*Math.PI*n)*r+t};function gt(t,r){return t.reduce((e,n,o)=>(e[n]=r?r(n,o):n,e),{})}exports.chance=ct;exports.createMap=gt;exports.err=b;exports.formatDate=it;exports.formatDateTime=ot;exports.formatDisplayDate=rt;exports.formatMonthDayTime=at;exports.formatNumberAbbreviated=O;exports.formatRelativeTime=et;exports.formatTickDate=nt;exports.formatTime=st;exports.getApiError=y;exports.getApiErrorMessage=D;exports.getItem=p.getItem;exports.hexToRGBA=h;exports.interpolateColor=C;exports.isValidEmail=q;exports.mergeById=T;exports.normalizeAddress=A;exports.ok=$;exports.omit=F;exports.onceAsync=U;exports.onceAsyncByKey=W;exports.pickColor=P;exports.pickRandom=dt;exports.pollUntil=G;exports.pruneArray=R;exports.randomBetween=ut;exports.randomBool=S;exports.randomGaussian=mt;exports.randomInt=ft;exports.randomSign=lt;exports.removeItem=p.removeItem;exports.rgbToRgba=B;exports.safeArray=J;exports.safeMaybe=K;exports.safeNilable=z;exports.safeNullable=Y;exports.safeNum=X;exports.safeOr=L;exports.safeStringable=x;exports.safeZeroable=Z;exports.setItem=p.setItem;exports.singleton=Q;exports.suspenseIfNull=tt;exports.toDatabasePrice=V;exports.toDisplayPrice=w;exports.toDisplayPriceNumber=H;exports.toNumberInputValue=j;exports.toRgbaWithAlpha=v;exports.toTextInputValue=_;exports.withRetryResult=M;
|
|
4
4
|
|
|
5
5
|
//# sourceMappingURL=utils.cjs.map
|
package/dist/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs","names":[],"sources":["../src/utils/address.ts","../src/utils/apiError.ts","../src/utils/array.ts","../src/utils/colors.ts","../src/utils/email.ts","../src/utils/form.ts","../src/utils/object.ts","../src/utils/result.ts","../src/utils/retry.ts","../src/utils/polling.ts","../src/utils/price.ts","../src/utils/promise.ts","../src/utils/safe.ts","../src/utils/singleton.ts","../src/utils/suspense.ts","../src/utils/time.ts","../src/utils/types.ts"],"sourcesContent":["/**\n * 规范化地址字符串:去除首尾空白并将连续空白合并为单个空格\n * Normalize address string: trim and collapse consecutive spaces to one.\n * @param address - 原始地址字符串\n * @returns 规范化后的地址\n */\nexport function normalizeAddress(address: string): string {\n return address.trim().replace(/\\s+/g, \" \");\n}\n","/**\n * 与约定一致:HTTP 错误经 fiberx.Error/ErrorFromErrx → httpx.BuildErrorResp,\n * 响应体固定为 { status, err_code, message, details?, trace_id? }(axios 转 camelCase)。\n * 前端只认「有 response.data」= 后端错误体;UI 展示与 NFX 一致:先 errors 命名空间(后端拉取),再 message,再 fallback。\n */\n\nimport type { AxiosError } from \"axios\";\nimport type { ApiErrorBody } from \"@/types/api\";\n\nimport i18n from \"@/languages/languages/i18n\";\n\n/**\n * 仅当为后端错误(有 response.data)时解析为 ApiErrorBody,否则返回 null\n * Parse to ApiErrorBody only when response.data exists; otherwise null.\n * @param error - 通常为 Axios 错误或 unknown\n * @returns ApiErrorBody 或 null\n */\nexport function getApiError(error: unknown): ApiErrorBody | null {\n const d = (error as AxiosError<ApiErrorBody>)?.response?.data;\n if (!d || typeof d !== \"object\") return null;\n return {\n status: d.status,\n errCode: d.errCode,\n message: d.message,\n details: d.details,\n traceId: d.traceId,\n };\n}\n\n/**\n * UI 展示用错误文案:优先 i18n errors 命名空间(errCode),其次 api.message,最后 fallback\n * Get display message: i18n errors namespace (by errCode) → api.message → fallback.\n * @param error - 通常为 Axios 错误\n * @param fallback - 无法解析时的默认文案\n * @returns 展示用字符串\n */\nexport function getApiErrorMessage(error: unknown, fallback: string): string {\n const api = getApiError(error);\n if (api?.errCode) {\n const out = i18n.t(`errors:${api.errCode}`);\n if (out && out !== `errors:${api.errCode}`) return out;\n }\n if (api?.message) return api.message;\n return fallback;\n}\n","import type { Array } from \"@/types\";\n\n/**\n * 按 id 将 items 合并进数组:insert 仅插入新 id,upsert 覆盖同 id 并插入新 id;可 prepend 或 append\n * Merge items into array by id: insert = only new ids, upsert = overwrite same id + insert new; place = prepend | append.\n * @param arr - 原数组\n * @param items - 待合并项\n * @param idOf - 取 id 的函数\n * @param place - 新项插入位置\n * @param mode - insert 不覆盖已有 id;upsert 覆盖同 id\n * @returns 合并后的新数组\n */\nexport function mergeById<T>(arr: Array<T>, items: Array<T>, idOf: (x: T) => string, place: \"prepend\" | \"append\", mode: \"insert\" | \"upsert\") {\n if (items.length === 0) return arr;\n\n switch (mode) {\n case \"insert\":\n return insertUnique(arr, items, idOf, place);\n case \"upsert\":\n return upsertArray(arr, items, idOf, place);\n }\n}\n\nfunction insertUnique<T>(arr: Array<T>, items: Array<T>, idOf: (x: T) => string, place: \"prepend\" | \"append\") {\n const seen = new Set(arr.map(idOf));\n\n const toInsert = items.filter((x) => {\n const id = idOf(x);\n if (seen.has(id)) return false;\n seen.add(id);\n return true;\n });\n if (toInsert.length === 0) return arr;\n\n return place === \"prepend\" ? [...toInsert, ...arr] : [...arr, ...toInsert];\n}\n\nfunction upsertArray<T>(arr: Array<T>, items: Array<T>, idOf: (x: T) => string, place: \"prepend\" | \"append\") {\n const idxById = new Map<string, number>();\n arr.forEach((x, i) => idxById.set(idOf(x), i));\n\n let changed = false;\n const next = arr.slice();\n\n // Overwrite: elements with the same id are replaced with the real items\n for (const it of items) {\n const id = idOf(it);\n const idx = idxById.get(id);\n if (idx !== undefined) {\n if (next[idx] !== it) {\n next[idx] = it;\n changed = true;\n }\n }\n }\n\n // Insert: elements with the same id are replaced with the real items\n const toInsert: Array<T> = [];\n for (const it of items) {\n const id = idOf(it);\n if (!idxById.has(id)) toInsert.push(it);\n }\n if (toInsert.length > 0) {\n changed = true;\n return place === \"prepend\" ? [...toInsert, ...next] : [...next, ...toInsert];\n }\n\n return changed ? next : arr;\n}\n\n/**\n * 从数组中移除 id 在集合 ids 中的项,返回新数组\n * Remove elements whose id is in the given set; return new array.\n * @param arr - 原数组\n * @param ids - 要移除的 id 集合\n * @param idOf - 取 id 的函数\n * @returns 若无变化返回原数组,否则返回新数组\n */\nexport function pruneArray<T>(arr: Array<T>, ids: ReadonlySet<string>, idOf: (x: T) => string) {\n let changed = false;\n const next = arr.filter((v) => {\n const keep = !ids.has(idOf(v));\n if (!keep) changed = true;\n return keep;\n });\n return changed ? next : arr;\n}\n","/**\n * 将任意颜色统一为指定透明度的 RGBA(rgb/rgba/hex 均用新 alpha 覆盖)\n * Convert any color to RGBA with given alpha (overwrites existing alpha).\n * @param color - rgb / rgba / hex 字符串\n * @param alpha - 透明度 0–1\n * @returns rgba(r, g, b, alpha) 字符串\n */\nexport const toRgbaWithAlpha = (color: string, alpha: number): string => {\n const rgbaMatch = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*[\\d.]+)?\\)/);\n if (rgbaMatch) {\n const [, r, g, b] = rgbaMatch;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n return hexToRGBA(color, alpha);\n};\n\n/**\n * 将 RGB 颜色转换为 RGBA\n * @param rgb - RGB 颜色字符串,格式: \"rgb(r, g, b)\"\n * @param alpha - 透明度,范围 0-1\n * @returns RGBA 颜色字符串,格式: \"rgba(r, g, b, alpha)\"\n *\n * @example\n * rgbToRgba(\"rgb(250, 30, 22)\", 0.3) // \"rgba(250, 30, 22, 0.3)\"\n */\nexport const rgbToRgba = (rgb: string, alpha: number): string => {\n if (rgb.startsWith(\"rgba\")) return rgb;\n const match = rgb.match(/rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)/);\n if (match) {\n const [, r, g, b] = match;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n console.warn(\"Invalid RGB color format:\", rgb);\n return rgb;\n};\n\n/**\n * 将 HEX 颜色转换为 RGBA\n * @param hex - HEX 颜色字符串,格式: \"#RRGGBB\" 或 \"#RGB\"\n * @param alpha - 透明度,范围 0-1\n * @returns RGBA 颜色字符串,格式: \"rgba(r, g, b, alpha)\"\n *\n * @example\n * hexToRGBA(\"#FA1E16\", 0.3) // \"rgba(250, 30, 22, 0.3)\"\n */\nexport const hexToRGBA = (hex: string, alpha: number): string => {\n // 如果传入是 rgb 或者 rgba 直接返回\n if (hex.indexOf(\"rgb\") !== -1) return hex;\n\n let r = 0,\n g = 0,\n b = 0;\n\n // 去除开头的 '#' 符号\n if (hex.startsWith(\"#\")) {\n hex = hex.slice(1);\n }\n\n // 处理 3 位或 6 位十六进制颜色\n if (hex.length === 3) {\n r = parseInt(hex[0] + hex[0], 16);\n g = parseInt(hex[1] + hex[1], 16);\n b = parseInt(hex[2] + hex[2], 16);\n } else if (hex.length === 6) {\n r = parseInt(hex.slice(0, 2), 16);\n g = parseInt(hex.slice(2, 4), 16);\n b = parseInt(hex.slice(4, 6), 16);\n } else {\n console.warn(\"Unsupported HEX color format\");\n }\n\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n};\n\n/**\n * 在两色之间按 factor 线性插值(支持 hex / rgb)\n * Linear color interpolation between start and end by factor (0–1); supports hex and rgb.\n * @param start - 起始颜色\n * @param end - 结束颜色\n * @param factor - 插值系数 0–1\n * @returns rgb(r, g, b) 字符串\n */\nexport const interpolateColor = (start: string, end: string, factor: number): string => {\n const parse = (color: string): [number, number, number] => {\n const hex = color.match(/^#?([\\da-f]{2})([\\da-f]{2})([\\da-f]{2})$/i);\n if (hex) return [parseInt(hex[1], 16), parseInt(hex[2], 16), parseInt(hex[3], 16)];\n const rgb = color.match(/^rgb\\((\\d{1,3}),\\s*(\\d{1,3}),\\s*(\\d{1,3})\\)$/i);\n if (rgb) return [parseInt(rgb[1]), parseInt(rgb[2]), parseInt(rgb[3])];\n return [0, 0, 0];\n };\n const [r1, g1, b1] = parse(start);\n const [r2, g2, b2] = parse(end);\n const r = Math.round(r1 + (r2 - r1) * factor);\n const g = Math.round(g1 + (g2 - g1) * factor);\n const b = Math.round(b1 + (b2 - b1) * factor);\n return `rgb(${r}, ${g}, ${b})`;\n};\n","/**\n * 校验邮箱格式是否有效(简单正则:含 @ 与点)\n * Check if email format is valid (simple regex: contains @ and dot).\n * @param email - 待校验的邮箱字符串\n * @returns 是否有效\n */\nexport const isValidEmail = (email: string): boolean => {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return emailRegex.test(email);\n};\n","/**\n * 将任意值转为文本输入框可用的字符串(用于表单回显)\n * Convert any value to a string suitable for text input (e.g. form display).\n * @param value - 任意值\n * @returns 字符串;null/undefined → \"\";数组 → 逗号拼接;对象 → JSON 字符串\n */\nexport function toTextInputValue(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n if (Array.isArray(value)) return value.map((item) => String(item)).join(\", \");\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * 将任意值转为数字输入框可用的字符串(用于表单回显)\n * Convert any value to a string suitable for number input (e.g. form display).\n * @param value - 任意值\n * @returns 字符串;null/undefined → \"\";非有限数字 → \"\";数组取首项\n */\nexport function toNumberInputValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"number\") return Number.isFinite(value) ? String(value) : \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"boolean\") return value ? \"1\" : \"0\";\n if (Array.isArray(value) && value.length > 0) return String(value[0]);\n return \"\";\n}\n","/**\n * 从对象中剔除指定键,返回新对象(不修改原对象)\n * Omit specified keys from object and return a new object.\n * @param obj - 源对象\n * @param keys - 要剔除的键(数组或单个键,可再接多个键)\n * @returns 剔除后的新对象\n * @example omit({ a: 1, b: 2, c: 3 }, ['a', 'c']) // { b: 2 }\n * @example omit({ a: 1, b: 2, c: 3 }, 'a', 'b') // { c: 3 }\n */\nexport function omit<Obj extends object, Key extends keyof Obj>(obj: Obj, keys: Key[] | Key, ...restKeys: Key[]): Omit<Obj, Key> {\n const result = {} as Omit<Obj, Key>;\n\n const removeKeys = Array.isArray(keys) ? keys : [keys, ...restKeys];\n const removeSet = new Set(removeKeys);\n\n for (const key of Object.keys(obj) as Key[]) {\n if (!removeSet.has(key)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (result as any)[key] = obj[key];\n }\n }\n\n return result;\n}\n","/**\n * 结果类型:成功为 [T, null],失败为 [null, Error]。\n * Result type: success [T, null], failure [null, Error].\n * @template T - 成功时的值类型。Value type on success.\n * @example const [data, err] = await withRetryResult(() => fetchData()); if (err) return; use(data);\n */\nexport type Result<T> = [T, null] | [null, Error];\n\n/**\n * 构造成功结果。Create a successful result.\n * @param v - 结果值。Result value.\n * @returns [v, null]\n * @example ok(42) // [42, null]\n */\nexport const ok = <T>(v: T): Result<T> => [v, null];\n\n/**\n * 构造失败结果(非 Error 会包装为 Error)。Create a failed result; non-Error wrapped in Error.\n * @param e - 错误或任意值。Error or any value.\n * @returns [null, Error]\n * @example err(new Error(\"fail\")) // [null, Error]\n */\nexport const err = (e: unknown): Result<never> => [null, e instanceof Error ? e : new Error(String(e))];\n","import retry from \"async-retry\";\nimport type { Options as RetryOptions } from \"async-retry\";\n\nimport { err, ok } from \"./result\";\nimport type { Result } from \"./result\";\n\nexport type WithRetryOptions = RetryOptions & {\n /** 若返回 true 则不再重试,直接 bail。If true, do not retry and bail. */\n isNonRetryable?: (e: Error) => boolean;\n};\n\n/**\n * 带重试执行异步函数,成功返回 ok(value),失败返回 err(error)。\n * Retry async function until success or max attempts; returns Result.\n * @param fn - 可接收 bail 与 attempt 的异步函数。Async function receiving bail and attempt.\n * @param opts - async-retry 选项及 isNonRetryable。async-retry options and isNonRetryable.\n * @returns Promise<Result<T>>\n * @example const [data, err] = await withRetryResult(() => fetchData(), { retries: 3 });\n */\nexport async function withRetryResult<T>(fn: (bail: (e: Error) => void, attempt: number) => Promise<T>, opts?: WithRetryOptions): Promise<Result<T>> {\n try {\n const value = await retry(async (bail: (e: Error) => void, attempt: number) => {\n try {\n return await fn(bail, attempt);\n } catch (e) {\n if (opts?.isNonRetryable?.(e as Error)) bail(e as Error);\n throw e;\n }\n }, opts);\n return ok(value);\n } catch (e) {\n return err(e);\n }\n}\n","import type { Result } from \"./result\";\nimport type { WithRetryOptions } from \"./retry\";\n\nimport { withRetryResult } from \"./retry\";\n\n/**\n * 轮询直到 isOK(data) 为 true(内部用 withRetryResult 重试)。\n * Poll until fetcher returns data that satisfies isOK; uses withRetryResult.\n * @param fetcher - 拉取数据的函数。Function that fetches data.\n * @param isOK - 判断数据是否满足条件。Predicate to check if data is OK.\n * @param opts - 重试选项。Retry options.\n * @returns Promise<Result<T>>\n * @example const [job] = await pollUntil(() => getJob(id), (j) => j.status === \"done\", { retries: 10 });\n */\nexport async function pollUntil<T>(fetcher: () => Promise<T>, isOK: (data: T) => boolean, opts?: WithRetryOptions): Promise<Result<T>> {\n return withRetryResult<T>(async (_bail, _attempt) => {\n const data = await fetcher();\n if (!isOK(data)) {\n throw new Error(\"Condition not satisfied\");\n }\n return data;\n }, opts);\n}\n","/**\n * 显示价格 → 数据库价格(分)。用于提交到后端、保存草稿等\n * Display price to database price (cents). For submit/save.\n * @param displayPrice - 展示用价格(元)\n * @returns 数据库存储价格(分)\n */\nexport function toDatabasePrice(displayPrice: number): number {\n return Math.round(Number(displayPrice) * 100);\n}\n\n/**\n * 数据库价格(分)→ 显示价格字符串(两位小数)\n * Database price (cents) to display string (2 decimal places).\n * @param databasePrice - 数据库价格(分)\n * @returns 如 \"12.34\"\n */\nexport function toDisplayPrice(databasePrice: number): string {\n return (Number(databasePrice) / 100).toFixed(2);\n}\n\n/**\n * 数据库价格(分)→ 显示价格数字。用于表单、计算等\n * Database price (cents) to display number.\n * @param databasePrice - 数据库价格(分)\n * @returns 数字(元)\n */\nexport function toDisplayPriceNumber(databasePrice: number): number {\n return Number(toDisplayPrice(databasePrice));\n}\n\n/**\n * 数字缩写显示(如 1.2k+、3.5M+、1.1B+)\n * Format number to abbreviated string (e.g. 1.2k+, 3.5M+, 1.1B+).\n * @param num - 数字\n * @returns 缩写字符串\n */\nexport function formatNumberAbbreviated(num: number): string {\n if (num < 1_000) {\n return num.toString();\n }\n\n if (num < 1_000_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, \"\")}k+`;\n }\n\n if (num < 1_000_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, \"\")}M+`;\n }\n\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, \"\")}B+`;\n}\n","/**\n * 包装为「仅执行一次」的异步函数(重复调用返回同一 Promise)。\n * Wrap async function so it runs only once; repeated calls return the same promise.\n * @param fn - 要执行的异步函数。Async function to run once.\n * @returns 包装后的函数,多次调用只执行一次。Wrapped function; only first call runs.\n * @example const loadOnce = onceAsync(() => fetch(\"/api/config\")); loadOnce(); loadOnce(); // 只请求一次\n */\nexport function onceAsync<T, Args extends unknown[]>(fn: (...args: Args) => Promise<T>) {\n let promise: Promise<T> | null = null;\n\n return async (...args: Args) => {\n if (promise) return promise;\n promise = fn(...args).finally(() => (promise = null));\n return promise;\n };\n}\n\n/**\n * 按 key 仅执行一次的异步函数(同一 key 重复调用返回同一 Promise)。\n * Async function that runs only once per key; same key returns same promise.\n * @param fn - 要执行的异步函数。Async function to run.\n * @param keyExtractor - 从参数中提取 key 的函数。Function to extract key from args.\n * @returns 包装后的函数。Wrapped function.\n * @example const fetchUser = onceAsyncByKey((id: string) => api.getUser(id), (id) => id); fetchUser(\"1\"); fetchUser(\"1\"); // 同 id 只请求一次\n */\nexport function onceAsyncByKey<T, Args extends unknown[]>(fn: (...args: Args) => Promise<T>, keyExtractor: (...args: Args) => string) {\n const promises = new Map<string, Promise<T>>();\n\n return async (...args: Args) => {\n const key = keyExtractor(...args);\n const existingPromise = promises.get(key);\n if (existingPromise) return existingPromise;\n\n const promise = fn(...args).finally(() => promises.delete(key));\n promises.set(key, promise);\n return promise;\n };\n}\n","/**\n * Safe 工具:将 Nilable / Emptyable / Array 等规范为 Nullable、Maybe、Zeroable、Stringable、Array,避免到处写 ?? undefined / ?? null / ?? 0 / ?? \"\" / ?? []。\n * Safe utils: normalize Nilable/Emptyable/array to Nullable, Maybe, Zeroable, Stringable, Array.\n */\n\nimport type { Nullable, Maybe, Nilable, Emptyable, Zeroable, Stringable, Array } from \"@/types\";\n\n/**\n * 将 Nilable 规范为 Nullable:undefined 转为 null,null 与 T 原样返回。返回类型 Nullable<T>。\n * Normalize Nilable to Nullable: convert undefined to null; null and T unchanged. Returns Nullable<T>.\n * @param value - 可能为 null 或 undefined 的值 (value that may be null or undefined)\n * @returns 若为 undefined 则 null,否则原值 (null if undefined, otherwise value)\n * @example safeNullable(apiResponse.data) // undefined → null,便于区分「未设置」与「空」\n */\nexport function safeNullable<T>(value: Nilable<T>): Nullable<T> {\n return (value === undefined ? null : value) as Nullable<T>;\n}\n\n/**\n * 将 Nilable 规范为 Maybe:null 转为 undefined,undefined 与 T 原样返回。返回类型 Maybe<T>。等同 value ?? undefined。\n * Normalize Nilable to Maybe: convert null to undefined; undefined and T unchanged. Returns Maybe<T>. Same as value ?? undefined.\n * @param value - 可能为 null 或 undefined 的值 (value that may be null or undefined)\n * @returns 若为 null 或 undefined 则 undefined,否则原值 (undefined if null/undefined, otherwise value)\n * @example safeMaybe(product.price) // 代替 product.price ?? undefined\n */\nexport function safeMaybe<T>(value: Nilable<T>): Maybe<T> {\n return value ?? undefined;\n}\n\n/**\n * 将 Emptyable 字符串规范为 Nilable:空字符串转为 undefined,null/undefined/非空串原样。返回类型 Nilable<T>。常用于表单、接口字段。\n * Normalize Emptyable string to Nilable: \"\" to undefined; null, undefined, non-empty string unchanged. Returns Nilable<T>. For form/API fields.\n * @param value - 可能为 null、undefined 或空字符串 (value that may be null, undefined or \"\")\n * @returns 若为 \"\" 则 undefined,否则原值 (undefined if \"\", otherwise value)\n * @example safeNilable(product.remark) // \"\" 也视为「无值」\n */\nexport function safeNilable<T extends string>(value: Emptyable<T>): Nilable<T> {\n return (value === \"\" ? undefined : value) as Nilable<T>;\n}\n\n/**\n * 将 Nilable 数组规范为数组:null/undefined 转为 [] 或 defaultValue。返回类型 Array<T>(即 T[])。\n * Normalize Nilable array to array: null/undefined to [] or defaultValue. Returns Array<T> (i.e. T[]).\n * @param value - 可能为 null 或 undefined 的数组 (array that may be null or undefined)\n * @param defaultValue - 可选,当 value 为 null/undefined 时使用;缺省为 [] (optional default when value is null/undefined; default is [])\n * @returns value ?? defaultValue ?? [] (never null/undefined)\n * @example safeArray(product.tags) // product.tags ?? []\n * @example safeArray(product.tags, ['default'])\n */\nexport function safeArray<T>(value: Nilable<Array<T>>, defaultValue?: Array<T>): Array<T> {\n return (value ?? defaultValue ?? []) as Array<T>;\n}\n\n/**\n * 将 Nilable 数值规范为 Zeroable:null/undefined 转为 0。返回类型 Zeroable<number>(即 number,0 为兜底)。\n * Normalize Nilable number to Zeroable: null/undefined to 0. Returns Zeroable<number> (number with 0 as fallback).\n * @param value - 可能为 null 或 undefined 的数值 (number that may be null or undefined)\n * @returns value ?? 0 (never null/undefined)\n * @example safeZeroable(product.stock) // 代替 product.stock ?? 0\n */\nexport function safeZeroable(value: Nilable<number>): Zeroable<number> {\n return (value ?? 0) as Zeroable<number>;\n}\n\n/**\n * 将 Emptyable 字符串规范为 Stringable:null/undefined/\"\" 转为 \"\"。返回类型 Stringable<string>(即 string,\"\" 为兜底)。\n * Normalize Emptyable string to Stringable: null/undefined/\"\" to \"\". Returns Stringable<string> (string with \"\" as fallback).\n * @param value - 可能为 null、undefined 或空字符串 (value that may be null, undefined or \"\")\n * @returns 若为 null、undefined 或 \"\" 则 \"\",否则原值 (\"\" if null/undefined/\"\", otherwise value)\n * @example safeStringable(product.name) // 代替 product.name ?? \"\"\n */\nexport function safeStringable<T extends string>(value: Emptyable<T>): Stringable<T> {\n if (value === null || value === undefined || value === \"\") return \"\" as Stringable<T>;\n return value as Stringable<T>;\n}\n\n/**\n * 若值为 null/undefined 则返回默认值,否则返回原值。等同 value ?? defaultValue。\n * Return defaultValue when value is null/undefined, otherwise value. Same as value ?? defaultValue.\n * @param value - 可能为 null 或 undefined 的值 (value that may be null or undefined)\n * @param defaultValue - 默认值 (default value)\n * @returns value ?? defaultValue\n * @example safeOr(product.stock, 0)\n * @example safeOr(product.name, '')\n */\nexport function safeOr<T, D>(value: Nilable<T>, defaultValue: D): T | D {\n return value ?? defaultValue;\n}\n\n/**\n * 安全取数字:null/undefined/NaN 转为 undefined,合法数字原样。返回类型 Maybe<number>。\n * Safe number: null/undefined/NaN to undefined; valid number unchanged. Returns Maybe<number>.\n * @param value - 可能为 null、undefined 或非数字 (value that may be null, undefined or NaN)\n * @returns 若为 null、undefined 或 NaN 则 undefined,否则数字 (undefined if null/undefined/NaN, otherwise number)\n * @example safeNum(product.price) // 用于严格「有值才用」的场景\n */\nexport function safeNum(value: Nilable<number>): Maybe<number> {\n if (value === null || value === undefined) return undefined;\n const n = Number(value);\n return Number.isNaN(n) ? undefined : n;\n}\n","/** 可实例化的构造函数类型。Instantiable constructor type. */\ntype Constructor<T extends object = object> = new (...args: unknown[]) => T;\n\n/** 深度比较两个值是否相等(支持 primitives、数组、plain objects)。Deep equal for primitives, arrays, plain objects. */\nfunction isEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n if (typeof a !== \"object\" || typeof b !== \"object\") return a === b;\n\n const arrA = Array.isArray(a);\n const arrB = Array.isArray(b);\n if (arrA !== arrB) return false;\n if (arrA && arrB) {\n if (a.length !== b.length) return false;\n return a.every((v, i) => isEqual(v, (b as unknown[])[i]));\n }\n\n const keysA = Object.keys(a as object).sort();\n const keysB = Object.keys(b as object).sort();\n if (keysA.length !== keysB.length) return false;\n if (!keysA.every((k, i) => k === keysB[i])) return false;\n return keysA.every((k) => isEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]));\n}\n\n/**\n * 单例工厂:将传入的构造函数包装为单例模式;Proxy 拦截 construct,多次 new 返回同一实例。\n * Singleton factory: wrap constructor so that multiple new calls return the same instance.\n *\n * @template T - 实例类型(需为 object)。Instance type (must be object).\n * @param className - 被包装的构造函数(class)。Constructor (class) to wrap.\n * @returns 代理后的构造函数,调用时始终返回同一实例。Proxied constructor returning same instance.\n *\n * @example\n * ```ts\n * class MyService { ... }\n * const SingletonService = singleton(MyService);\n * const a = new SingletonService();\n * const b = new SingletonService();\n * console.log(a === b); // true\n * ```\n */\nexport const singleton = <T extends object>(className: Constructor<T>): Constructor<T> => {\n let instance: T | undefined;\n let parameters: unknown[] = [];\n return new Proxy(className, {\n construct(_target: Constructor<T>, args: unknown[]): T {\n if (!instance) {\n instance = new className(...args);\n parameters = args;\n }\n if (!isEqual(args, parameters)) {\n throw new Error(\"Cannot create multiple instances with different parameters\");\n }\n return instance;\n },\n }) as Constructor<T>;\n};\n","import type { Nilable } from \"@/types\";\n\n/**\n * 若值为 null/undefined 则抛出 Promise 使 React Suspense 挂起,否则返回该值。\n * If value is null/undefined, throw a promise to suspend (React Suspense); otherwise return value.\n * @param value - 待检查的值。Value to check.\n * @param delay - 重试前的延迟(毫秒)。Delay in ms before retry.\n * @returns 非空时的 value。Value when non-null.\n * @example const data = suspenseIfNull(resource); // 在 Suspense 边界内,data 非空时才渲染\n */\nexport function suspenseIfNull<T>(value: Nilable<T>, delay = 100): T {\n if (value == null) {\n throw new Promise<void>((resolve) => setTimeout(resolve, delay));\n }\n return value;\n}\n","/**\n * 格式化日期为 YYYY/MM/DD(空或无效返回 \"\")\n * Format date as YYYY/MM/DD; empty or invalid returns \"\".\n * @param dateString - 日期字符串\n * @returns 格式化后的日期字符串,无效则 \"\"\n */\nexport const formatDisplayDate = (dateString: string | undefined | null): string => {\n if (!dateString) return \"\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return \"\";\n const year = date.getFullYear();\n const month = (date.getMonth() + 1).toString().padStart(2, \"0\");\n const day = date.getDate().toString().padStart(2, \"0\");\n return `${year}/${month}/${day}`;\n};\n\n/**\n * 格式化为相对时间(如 2 days ago, 3 months ago)\n * Format as relative time (e.g. 2 days ago, 3 months ago).\n * @param dateString - 日期字符串\n * @returns 相对时间字符串,无效为 \"Invalid date\",空为 \"Unknown\"\n */\nexport const formatRelativeTime = (dateString: string | undefined | null): string => {\n if (!dateString) return \"Unknown\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return \"Invalid date\";\n\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffSeconds = Math.floor(diffMs / 1000);\n const diffMinutes = Math.floor(diffSeconds / 60);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n const diffMonths = Math.floor(diffDays / 30);\n const diffYears = Math.floor(diffDays / 365);\n\n if (diffYears > 0) return `${diffYears} year${diffYears > 1 ? \"s\" : \"\"} ago`;\n if (diffMonths > 0) return `${diffMonths} month${diffMonths > 1 ? \"s\" : \"\"} ago`;\n if (diffDays > 0) return `${diffDays} day${diffDays > 1 ? \"s\" : \"\"} ago`;\n if (diffHours > 0) return `${diffHours} hour${diffHours > 1 ? \"s\" : \"\"} ago`;\n if (diffMinutes > 0) return `${diffMinutes} minute${diffMinutes > 1 ? \"s\" : \"\"} ago`;\n return \"Just now\";\n};\n\n/**\n * 格式化 ISO 日期为图表轴/提示用短格式(如:Jan 4, 02:30 PM)\n * Format ISO date for chart axis/tooltip (e.g. Jan 4, 02:30 PM).\n * @param iso - ISO 日期字符串\n * @returns 格式化字符串,解析失败则返回原串\n */\nexport function formatTickDate(iso: string): string {\n try {\n const d = new Date(iso);\n return d.toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n } catch {\n return iso;\n }\n}\n\n/**\n * 格式化时间显示年月日时分 (YYYY/MM/DD HH:mm)\n * Format date-time as year/month/day hour:minute.\n * @param dateString - 时间字符串\n * @returns 格式化后的时间字符串,无效则返回原串\n */\nexport const formatDateTime = (dateString: string): string => {\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return dateString;\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const hour = String(date.getHours()).padStart(2, \"0\");\n const minute = String(date.getMinutes()).padStart(2, \"0\");\n return `${year}/${month}/${day} ${hour}:${minute}`;\n } catch {\n return dateString;\n }\n};\n\n/**\n * 格式化时间显示月日时分 (MM/DD HH:mm)\n * Format date-time as month/day hour:minute.\n * @param dateString - 时间字符串\n * @returns 格式化后的时间字符串,无效则返回原串\n */\nexport const formatMonthDayTime = (dateString: string): string => {\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return dateString;\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const hour = String(date.getHours()).padStart(2, \"0\");\n const minute = String(date.getMinutes()).padStart(2, \"0\");\n return `${month}/${day} ${hour}:${minute}`;\n } catch {\n return dateString;\n }\n};\n\n/**\n * 格式化日期仅年月日 (YYYY/MM/DD),用于生日等\n * Format date as year/month/day only.\n * @param dateString - 日期字符串,如 \"2000-01-15\" 或 ISO\n * @returns 格式化后的日期字符串,无效则返回原串\n */\nexport const formatDate = (dateString: string): string => {\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return dateString;\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}/${month}/${day}`;\n } catch {\n return dateString;\n }\n};\n\n/**\n * 格式化时间显示时分 (HH:mm)\n * Format time as hour:minute.\n * @param dateString - 时间字符串\n * @returns 格式化后的时间字符串,无效则返回原串\n */\nexport const formatTime = (dateString: string): string => {\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return dateString;\n const hour = String(date.getHours()).padStart(2, \"0\");\n const minute = String(date.getMinutes()).padStart(2, \"0\");\n return `${hour}:${minute}`;\n } catch {\n return dateString;\n }\n};\n","/**\n * 从字符串列表创建键值映射对象;可选 valueMapper 指定值\n * Create record from string list; optional valueMapper for values.\n * @param list - 字符串数组(作为键)\n * @param valueMapper - 可选,(item, index) => value,缺省时值为键本身\n * @returns Record<list[number], V>\n * @example createMap([\"a\",\"b\",\"c\"]) // { a: \"a\", b: \"b\", c: \"c\" }\n * @example createMap([\"a\",\"b\",\"c\"], (_, i) => i) // { a: 0, b: 1, c: 2 }\n */\nexport function createMap<T extends string, V = T>(list: ReadonlyArray<T>, valueMapper?: (item: T, index: number) => V): Record<T, V> {\n return list.reduce(\n (acc, item, index) => {\n acc[item] = valueMapper ? valueMapper(item, index) : (item as unknown as V);\n return acc;\n },\n {} as Record<T, V>,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.cjs","names":[],"sources":["../src/utils/address.ts","../src/utils/apiError.ts","../src/utils/array.ts","../src/utils/colors.ts","../src/utils/email.ts","../src/utils/form.ts","../src/utils/object.ts","../src/utils/result.ts","../src/utils/retry.ts","../src/utils/polling.ts","../src/utils/price.ts","../src/utils/promise.ts","../src/utils/safe.ts","../src/utils/singleton.ts","../src/utils/suspense.ts","../src/utils/time.ts","../src/utils/random.ts","../src/utils/types.ts"],"sourcesContent":["/**\n * 规范化地址字符串:去除首尾空白并将连续空白合并为单个空格\n * Normalize address string: trim and collapse consecutive spaces to one.\n * @param address - 原始地址字符串\n * @returns 规范化后的地址\n */\nexport function normalizeAddress(address: string): string {\n return address.trim().replace(/\\s+/g, \" \");\n}\n","/**\n * 与约定一致:HTTP 错误经 fiberx.Error/ErrorFromErrx → httpx.BuildErrorResp,\n * 响应体固定为 { status, err_code, message, details?, trace_id? }(axios 转 camelCase)。\n * 前端只认「有 response.data」= 后端错误体;UI 展示与 NFX 一致:先 errors 命名空间(后端拉取),再 message,再 fallback。\n */\n\nimport type { AxiosError } from \"axios\";\nimport type { ApiErrorBody } from \"@/types/api\";\n\nimport i18n from \"@/languages/languages/i18n\";\n\n/**\n * 仅当为后端错误(有 response.data)时解析为 ApiErrorBody,否则返回 null\n * Parse to ApiErrorBody only when response.data exists; otherwise null.\n * @param error - 通常为 Axios 错误或 unknown\n * @returns ApiErrorBody 或 null\n */\nexport function getApiError(error: unknown): ApiErrorBody | null {\n const d = (error as AxiosError<ApiErrorBody>)?.response?.data;\n if (!d || typeof d !== \"object\") return null;\n return {\n status: d.status,\n errCode: d.errCode,\n message: d.message,\n details: d.details,\n traceId: d.traceId,\n };\n}\n\n/**\n * UI 展示用错误文案:优先 i18n errors 命名空间(errCode),其次 api.message,最后 fallback\n * Get display message: i18n errors namespace (by errCode) → api.message → fallback.\n * @param error - 通常为 Axios 错误\n * @param fallback - 无法解析时的默认文案\n * @returns 展示用字符串\n */\nexport function getApiErrorMessage(error: unknown, fallback: string): string {\n const api = getApiError(error);\n if (api?.errCode) {\n const out = i18n.t(`errors:${api.errCode}`);\n if (out && out !== `errors:${api.errCode}`) return out;\n }\n if (api?.message) return api.message;\n return fallback;\n}\n","import type { Array } from \"@/types\";\n\n/**\n * 按 id 将 items 合并进数组:insert 仅插入新 id,upsert 覆盖同 id 并插入新 id;可 prepend 或 append\n * Merge items into array by id: insert = only new ids, upsert = overwrite same id + insert new; place = prepend | append.\n * @param arr - 原数组\n * @param items - 待合并项\n * @param idOf - 取 id 的函数\n * @param place - 新项插入位置\n * @param mode - insert 不覆盖已有 id;upsert 覆盖同 id\n * @returns 合并后的新数组\n */\nexport function mergeById<T>(arr: Array<T>, items: Array<T>, idOf: (x: T) => string, place: \"prepend\" | \"append\", mode: \"insert\" | \"upsert\") {\n if (items.length === 0) return arr;\n\n switch (mode) {\n case \"insert\":\n return insertUnique(arr, items, idOf, place);\n case \"upsert\":\n return upsertArray(arr, items, idOf, place);\n }\n}\n\nfunction insertUnique<T>(arr: Array<T>, items: Array<T>, idOf: (x: T) => string, place: \"prepend\" | \"append\") {\n const seen = new Set(arr.map(idOf));\n\n const toInsert = items.filter((x) => {\n const id = idOf(x);\n if (seen.has(id)) return false;\n seen.add(id);\n return true;\n });\n if (toInsert.length === 0) return arr;\n\n return place === \"prepend\" ? [...toInsert, ...arr] : [...arr, ...toInsert];\n}\n\nfunction upsertArray<T>(arr: Array<T>, items: Array<T>, idOf: (x: T) => string, place: \"prepend\" | \"append\") {\n const idxById = new Map<string, number>();\n arr.forEach((x, i) => idxById.set(idOf(x), i));\n\n let changed = false;\n const next = arr.slice();\n\n // Overwrite: elements with the same id are replaced with the real items\n for (const it of items) {\n const id = idOf(it);\n const idx = idxById.get(id);\n if (idx !== undefined) {\n if (next[idx] !== it) {\n next[idx] = it;\n changed = true;\n }\n }\n }\n\n // Insert: elements with the same id are replaced with the real items\n const toInsert: Array<T> = [];\n for (const it of items) {\n const id = idOf(it);\n if (!idxById.has(id)) toInsert.push(it);\n }\n if (toInsert.length > 0) {\n changed = true;\n return place === \"prepend\" ? [...toInsert, ...next] : [...next, ...toInsert];\n }\n\n return changed ? next : arr;\n}\n\n/**\n * 从数组中移除 id 在集合 ids 中的项,返回新数组\n * Remove elements whose id is in the given set; return new array.\n * @param arr - 原数组\n * @param ids - 要移除的 id 集合\n * @param idOf - 取 id 的函数\n * @returns 若无变化返回原数组,否则返回新数组\n */\nexport function pruneArray<T>(arr: Array<T>, ids: ReadonlySet<string>, idOf: (x: T) => string) {\n let changed = false;\n const next = arr.filter((v) => {\n const keep = !ids.has(idOf(v));\n if (!keep) changed = true;\n return keep;\n });\n return changed ? next : arr;\n}\n","/**\n * 将任意颜色统一为指定透明度的 RGBA(rgb/rgba/hex 均用新 alpha 覆盖)\n * Convert any color to RGBA with given alpha (overwrites existing alpha).\n * @param color - rgb / rgba / hex 字符串\n * @param alpha - 透明度 0–1\n * @returns rgba(r, g, b, alpha) 字符串\n */\nexport const toRgbaWithAlpha = (color: string, alpha: number): string => {\n const rgbaMatch = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*[\\d.]+)?\\)/);\n if (rgbaMatch) {\n const [, r, g, b] = rgbaMatch;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n return hexToRGBA(color, alpha);\n};\n\n/**\n * 将 RGB 颜色转换为 RGBA\n * @param rgb - RGB 颜色字符串,格式: \"rgb(r, g, b)\"\n * @param alpha - 透明度,范围 0-1\n * @returns RGBA 颜色字符串,格式: \"rgba(r, g, b, alpha)\"\n *\n * @example\n * rgbToRgba(\"rgb(250, 30, 22)\", 0.3) // \"rgba(250, 30, 22, 0.3)\"\n */\nexport const rgbToRgba = (rgb: string, alpha: number): string => {\n if (rgb.startsWith(\"rgba\")) return rgb;\n const match = rgb.match(/rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)/);\n if (match) {\n const [, r, g, b] = match;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n console.warn(\"Invalid RGB color format:\", rgb);\n return rgb;\n};\n\n/**\n * 将 HEX 颜色转换为 RGBA\n * @param hex - HEX 颜色字符串,格式: \"#RRGGBB\" 或 \"#RGB\"\n * @param alpha - 透明度,范围 0-1\n * @returns RGBA 颜色字符串,格式: \"rgba(r, g, b, alpha)\"\n *\n * @example\n * hexToRGBA(\"#FA1E16\", 0.3) // \"rgba(250, 30, 22, 0.3)\"\n */\nexport const hexToRGBA = (hex: string, alpha: number): string => {\n // 如果传入是 rgb 或者 rgba 直接返回\n if (hex.indexOf(\"rgb\") !== -1) return hex;\n\n let r = 0,\n g = 0,\n b = 0;\n\n // 去除开头的 '#' 符号\n if (hex.startsWith(\"#\")) {\n hex = hex.slice(1);\n }\n\n // 处理 3 位或 6 位十六进制颜色\n if (hex.length === 3) {\n r = parseInt(hex[0] + hex[0], 16);\n g = parseInt(hex[1] + hex[1], 16);\n b = parseInt(hex[2] + hex[2], 16);\n } else if (hex.length === 6) {\n r = parseInt(hex.slice(0, 2), 16);\n g = parseInt(hex.slice(2, 4), 16);\n b = parseInt(hex.slice(4, 6), 16);\n } else {\n console.warn(\"Unsupported HEX color format\");\n }\n\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n};\n\n/**\n * 在两色之间按 factor 线性插值(支持 hex / rgb)\n * Linear color interpolation between start and end by factor (0–1); supports hex and rgb.\n * @param start - 起始颜色\n * @param end - 结束颜色\n * @param factor - 插值系数 0–1\n * @returns rgb(r, g, b) 字符串\n */\nexport const interpolateColor = (start: string, end: string, factor: number): string => {\n const parse = (color: string): [number, number, number] => {\n const hex = color.match(/^#?([\\da-f]{2})([\\da-f]{2})([\\da-f]{2})$/i);\n if (hex) return [parseInt(hex[1], 16), parseInt(hex[2], 16), parseInt(hex[3], 16)];\n const rgb = color.match(/^rgb\\((\\d{1,3}),\\s*(\\d{1,3}),\\s*(\\d{1,3})\\)$/i);\n if (rgb) return [parseInt(rgb[1]), parseInt(rgb[2]), parseInt(rgb[3])];\n return [0, 0, 0];\n };\n const [r1, g1, b1] = parse(start);\n const [r2, g2, b2] = parse(end);\n const r = Math.round(r1 + (r2 - r1) * factor);\n const g = Math.round(g1 + (g2 - g1) * factor);\n const b = Math.round(b1 + (b2 - b1) * factor);\n return `rgb(${r}, ${g}, ${b})`;\n};\n\n/**\n * Pick one color from a string pool; fallback to white.\n */\nexport const pickColor = (pool: string[]): string => {\n return pool[Math.floor(Math.random() * pool.length)] || \"#ffffff\";\n};\n","/**\n * 校验邮箱格式是否有效(简单正则:含 @ 与点)\n * Check if email format is valid (simple regex: contains @ and dot).\n * @param email - 待校验的邮箱字符串\n * @returns 是否有效\n */\nexport const isValidEmail = (email: string): boolean => {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return emailRegex.test(email);\n};\n","/**\n * 将任意值转为文本输入框可用的字符串(用于表单回显)\n * Convert any value to a string suitable for text input (e.g. form display).\n * @param value - 任意值\n * @returns 字符串;null/undefined → \"\";数组 → 逗号拼接;对象 → JSON 字符串\n */\nexport function toTextInputValue(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n if (Array.isArray(value)) return value.map((item) => String(item)).join(\", \");\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * 将任意值转为数字输入框可用的字符串(用于表单回显)\n * Convert any value to a string suitable for number input (e.g. form display).\n * @param value - 任意值\n * @returns 字符串;null/undefined → \"\";非有限数字 → \"\";数组取首项\n */\nexport function toNumberInputValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"number\") return Number.isFinite(value) ? String(value) : \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"boolean\") return value ? \"1\" : \"0\";\n if (Array.isArray(value) && value.length > 0) return String(value[0]);\n return \"\";\n}\n","/**\n * 从对象中剔除指定键,返回新对象(不修改原对象)\n * Omit specified keys from object and return a new object.\n * @param obj - 源对象\n * @param keys - 要剔除的键(数组或单个键,可再接多个键)\n * @returns 剔除后的新对象\n * @example omit({ a: 1, b: 2, c: 3 }, ['a', 'c']) // { b: 2 }\n * @example omit({ a: 1, b: 2, c: 3 }, 'a', 'b') // { c: 3 }\n */\nexport function omit<Obj extends object, Key extends keyof Obj>(obj: Obj, keys: Key[] | Key, ...restKeys: Key[]): Omit<Obj, Key> {\n const result = {} as Omit<Obj, Key>;\n\n const removeKeys = Array.isArray(keys) ? keys : [keys, ...restKeys];\n const removeSet = new Set(removeKeys);\n\n for (const key of Object.keys(obj) as Key[]) {\n if (!removeSet.has(key)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (result as any)[key] = obj[key];\n }\n }\n\n return result;\n}\n","/**\n * 结果类型:成功为 [T, null],失败为 [null, Error]。\n * Result type: success [T, null], failure [null, Error].\n * @template T - 成功时的值类型。Value type on success.\n * @example const [data, err] = await withRetryResult(() => fetchData()); if (err) return; use(data);\n */\nexport type Result<T> = [T, null] | [null, Error];\n\n/**\n * 构造成功结果。Create a successful result.\n * @param v - 结果值。Result value.\n * @returns [v, null]\n * @example ok(42) // [42, null]\n */\nexport const ok = <T>(v: T): Result<T> => [v, null];\n\n/**\n * 构造失败结果(非 Error 会包装为 Error)。Create a failed result; non-Error wrapped in Error.\n * @param e - 错误或任意值。Error or any value.\n * @returns [null, Error]\n * @example err(new Error(\"fail\")) // [null, Error]\n */\nexport const err = (e: unknown): Result<never> => [null, e instanceof Error ? e : new Error(String(e))];\n","import retry from \"async-retry\";\nimport type { Options as RetryOptions } from \"async-retry\";\n\nimport { err, ok } from \"./result\";\nimport type { Result } from \"./result\";\n\nexport type WithRetryOptions = RetryOptions & {\n /** 若返回 true 则不再重试,直接 bail。If true, do not retry and bail. */\n isNonRetryable?: (e: Error) => boolean;\n};\n\n/**\n * 带重试执行异步函数,成功返回 ok(value),失败返回 err(error)。\n * Retry async function until success or max attempts; returns Result.\n * @param fn - 可接收 bail 与 attempt 的异步函数。Async function receiving bail and attempt.\n * @param opts - async-retry 选项及 isNonRetryable。async-retry options and isNonRetryable.\n * @returns Promise<Result<T>>\n * @example const [data, err] = await withRetryResult(() => fetchData(), { retries: 3 });\n */\nexport async function withRetryResult<T>(fn: (bail: (e: Error) => void, attempt: number) => Promise<T>, opts?: WithRetryOptions): Promise<Result<T>> {\n try {\n const value = await retry(async (bail: (e: Error) => void, attempt: number) => {\n try {\n return await fn(bail, attempt);\n } catch (e) {\n if (opts?.isNonRetryable?.(e as Error)) bail(e as Error);\n throw e;\n }\n }, opts);\n return ok(value);\n } catch (e) {\n return err(e);\n }\n}\n","import type { Result } from \"./result\";\nimport type { WithRetryOptions } from \"./retry\";\n\nimport { withRetryResult } from \"./retry\";\n\n/**\n * 轮询直到 isOK(data) 为 true(内部用 withRetryResult 重试)。\n * Poll until fetcher returns data that satisfies isOK; uses withRetryResult.\n * @param fetcher - 拉取数据的函数。Function that fetches data.\n * @param isOK - 判断数据是否满足条件。Predicate to check if data is OK.\n * @param opts - 重试选项。Retry options.\n * @returns Promise<Result<T>>\n * @example const [job] = await pollUntil(() => getJob(id), (j) => j.status === \"done\", { retries: 10 });\n */\nexport async function pollUntil<T>(fetcher: () => Promise<T>, isOK: (data: T) => boolean, opts?: WithRetryOptions): Promise<Result<T>> {\n return withRetryResult<T>(async (_bail, _attempt) => {\n const data = await fetcher();\n if (!isOK(data)) {\n throw new Error(\"Condition not satisfied\");\n }\n return data;\n }, opts);\n}\n","/**\n * 显示价格 → 数据库价格(分)。用于提交到后端、保存草稿等\n * Display price to database price (cents). For submit/save.\n * @param displayPrice - 展示用价格(元)\n * @returns 数据库存储价格(分)\n */\nexport function toDatabasePrice(displayPrice: number): number {\n return Math.round(Number(displayPrice) * 100);\n}\n\n/**\n * 数据库价格(分)→ 显示价格字符串(两位小数)\n * Database price (cents) to display string (2 decimal places).\n * @param databasePrice - 数据库价格(分)\n * @returns 如 \"12.34\"\n */\nexport function toDisplayPrice(databasePrice: number): string {\n return (Number(databasePrice) / 100).toFixed(2);\n}\n\n/**\n * 数据库价格(分)→ 显示价格数字。用于表单、计算等\n * Database price (cents) to display number.\n * @param databasePrice - 数据库价格(分)\n * @returns 数字(元)\n */\nexport function toDisplayPriceNumber(databasePrice: number): number {\n return Number(toDisplayPrice(databasePrice));\n}\n\n/**\n * 数字缩写显示(如 1.2k+、3.5M+、1.1B+)\n * Format number to abbreviated string (e.g. 1.2k+, 3.5M+, 1.1B+).\n * @param num - 数字\n * @returns 缩写字符串\n */\nexport function formatNumberAbbreviated(num: number): string {\n if (num < 1_000) {\n return num.toString();\n }\n\n if (num < 1_000_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, \"\")}k+`;\n }\n\n if (num < 1_000_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, \"\")}M+`;\n }\n\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, \"\")}B+`;\n}\n","/**\n * 包装为「仅执行一次」的异步函数(重复调用返回同一 Promise)。\n * Wrap async function so it runs only once; repeated calls return the same promise.\n * @param fn - 要执行的异步函数。Async function to run once.\n * @returns 包装后的函数,多次调用只执行一次。Wrapped function; only first call runs.\n * @example const loadOnce = onceAsync(() => fetch(\"/api/config\")); loadOnce(); loadOnce(); // 只请求一次\n */\nexport function onceAsync<T, Args extends unknown[]>(fn: (...args: Args) => Promise<T>) {\n let promise: Promise<T> | null = null;\n\n return async (...args: Args) => {\n if (promise) return promise;\n promise = fn(...args).finally(() => (promise = null));\n return promise;\n };\n}\n\n/**\n * 按 key 仅执行一次的异步函数(同一 key 重复调用返回同一 Promise)。\n * Async function that runs only once per key; same key returns same promise.\n * @param fn - 要执行的异步函数。Async function to run.\n * @param keyExtractor - 从参数中提取 key 的函数。Function to extract key from args.\n * @returns 包装后的函数。Wrapped function.\n * @example const fetchUser = onceAsyncByKey((id: string) => api.getUser(id), (id) => id); fetchUser(\"1\"); fetchUser(\"1\"); // 同 id 只请求一次\n */\nexport function onceAsyncByKey<T, Args extends unknown[]>(fn: (...args: Args) => Promise<T>, keyExtractor: (...args: Args) => string) {\n const promises = new Map<string, Promise<T>>();\n\n return async (...args: Args) => {\n const key = keyExtractor(...args);\n const existingPromise = promises.get(key);\n if (existingPromise) return existingPromise;\n\n const promise = fn(...args).finally(() => promises.delete(key));\n promises.set(key, promise);\n return promise;\n };\n}\n","/**\n * Safe 工具:将 Nilable / Emptyable / Array 等规范为 Nullable、Maybe、Zeroable、Stringable、Array,避免到处写 ?? undefined / ?? null / ?? 0 / ?? \"\" / ?? []。\n * Safe utils: normalize Nilable/Emptyable/array to Nullable, Maybe, Zeroable, Stringable, Array.\n */\n\nimport type { Nullable, Maybe, Nilable, Emptyable, Zeroable, Stringable, Array } from \"@/types\";\n\n/**\n * 将 Nilable 规范为 Nullable:undefined 转为 null,null 与 T 原样返回。返回类型 Nullable<T>。\n * Normalize Nilable to Nullable: convert undefined to null; null and T unchanged. Returns Nullable<T>.\n * @param value - 可能为 null 或 undefined 的值 (value that may be null or undefined)\n * @returns 若为 undefined 则 null,否则原值 (null if undefined, otherwise value)\n * @example safeNullable(apiResponse.data) // undefined → null,便于区分「未设置」与「空」\n */\nexport function safeNullable<T>(value: Nilable<T>): Nullable<T> {\n return (value === undefined ? null : value) as Nullable<T>;\n}\n\n/**\n * 将 Nilable 规范为 Maybe:null 转为 undefined,undefined 与 T 原样返回。返回类型 Maybe<T>。等同 value ?? undefined。\n * Normalize Nilable to Maybe: convert null to undefined; undefined and T unchanged. Returns Maybe<T>. Same as value ?? undefined.\n * @param value - 可能为 null 或 undefined 的值 (value that may be null or undefined)\n * @returns 若为 null 或 undefined 则 undefined,否则原值 (undefined if null/undefined, otherwise value)\n * @example safeMaybe(product.price) // 代替 product.price ?? undefined\n */\nexport function safeMaybe<T>(value: Nilable<T>): Maybe<T> {\n return value ?? undefined;\n}\n\n/**\n * 将 Emptyable 字符串规范为 Nilable:空字符串转为 undefined,null/undefined/非空串原样。返回类型 Nilable<T>。常用于表单、接口字段。\n * Normalize Emptyable string to Nilable: \"\" to undefined; null, undefined, non-empty string unchanged. Returns Nilable<T>. For form/API fields.\n * @param value - 可能为 null、undefined 或空字符串 (value that may be null, undefined or \"\")\n * @returns 若为 \"\" 则 undefined,否则原值 (undefined if \"\", otherwise value)\n * @example safeNilable(product.remark) // \"\" 也视为「无值」\n */\nexport function safeNilable<T extends string>(value: Emptyable<T>): Nilable<T> {\n return (value === \"\" ? undefined : value) as Nilable<T>;\n}\n\n/**\n * 将 Nilable 数组规范为数组:null/undefined 转为 [] 或 defaultValue。返回类型 Array<T>(即 T[])。\n * Normalize Nilable array to array: null/undefined to [] or defaultValue. Returns Array<T> (i.e. T[]).\n * @param value - 可能为 null 或 undefined 的数组 (array that may be null or undefined)\n * @param defaultValue - 可选,当 value 为 null/undefined 时使用;缺省为 [] (optional default when value is null/undefined; default is [])\n * @returns value ?? defaultValue ?? [] (never null/undefined)\n * @example safeArray(product.tags) // product.tags ?? []\n * @example safeArray(product.tags, ['default'])\n */\nexport function safeArray<T>(value: Nilable<Array<T>>, defaultValue?: Array<T>): Array<T> {\n return (value ?? defaultValue ?? []) as Array<T>;\n}\n\n/**\n * 将 Nilable 数值规范为 Zeroable:null/undefined 转为 0。返回类型 Zeroable<number>(即 number,0 为兜底)。\n * Normalize Nilable number to Zeroable: null/undefined to 0. Returns Zeroable<number> (number with 0 as fallback).\n * @param value - 可能为 null 或 undefined 的数值 (number that may be null or undefined)\n * @returns value ?? 0 (never null/undefined)\n * @example safeZeroable(product.stock) // 代替 product.stock ?? 0\n */\nexport function safeZeroable(value: Nilable<number>): Zeroable<number> {\n return (value ?? 0) as Zeroable<number>;\n}\n\n/**\n * 将 Emptyable 字符串规范为 Stringable:null/undefined/\"\" 转为 \"\"。返回类型 Stringable<string>(即 string,\"\" 为兜底)。\n * Normalize Emptyable string to Stringable: null/undefined/\"\" to \"\". Returns Stringable<string> (string with \"\" as fallback).\n * @param value - 可能为 null、undefined 或空字符串 (value that may be null, undefined or \"\")\n * @returns 若为 null、undefined 或 \"\" 则 \"\",否则原值 (\"\" if null/undefined/\"\", otherwise value)\n * @example safeStringable(product.name) // 代替 product.name ?? \"\"\n */\nexport function safeStringable<T extends string>(value: Emptyable<T>): Stringable<T> {\n if (value === null || value === undefined || value === \"\") return \"\" as Stringable<T>;\n return value as Stringable<T>;\n}\n\n/**\n * 若值为 null/undefined 则返回默认值,否则返回原值。等同 value ?? defaultValue。\n * Return defaultValue when value is null/undefined, otherwise value. Same as value ?? defaultValue.\n * @param value - 可能为 null 或 undefined 的值 (value that may be null or undefined)\n * @param defaultValue - 默认值 (default value)\n * @returns value ?? defaultValue\n * @example safeOr(product.stock, 0)\n * @example safeOr(product.name, '')\n */\nexport function safeOr<T, D>(value: Nilable<T>, defaultValue: D): T | D {\n return value ?? defaultValue;\n}\n\n/**\n * 安全取数字:null/undefined/NaN 转为 undefined,合法数字原样。返回类型 Maybe<number>。\n * Safe number: null/undefined/NaN to undefined; valid number unchanged. Returns Maybe<number>.\n * @param value - 可能为 null、undefined 或非数字 (value that may be null, undefined or NaN)\n * @returns 若为 null、undefined 或 NaN 则 undefined,否则数字 (undefined if null/undefined/NaN, otherwise number)\n * @example safeNum(product.price) // 用于严格「有值才用」的场景\n */\nexport function safeNum(value: Nilable<number>): Maybe<number> {\n if (value === null || value === undefined) return undefined;\n const n = Number(value);\n return Number.isNaN(n) ? undefined : n;\n}\n","/** 可实例化的构造函数类型。Instantiable constructor type. */\ntype Constructor<T extends object = object> = new (...args: unknown[]) => T;\n\n/** 深度比较两个值是否相等(支持 primitives、数组、plain objects)。Deep equal for primitives, arrays, plain objects. */\nfunction isEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n if (typeof a !== \"object\" || typeof b !== \"object\") return a === b;\n\n const arrA = Array.isArray(a);\n const arrB = Array.isArray(b);\n if (arrA !== arrB) return false;\n if (arrA && arrB) {\n if (a.length !== b.length) return false;\n return a.every((v, i) => isEqual(v, (b as unknown[])[i]));\n }\n\n const keysA = Object.keys(a as object).sort();\n const keysB = Object.keys(b as object).sort();\n if (keysA.length !== keysB.length) return false;\n if (!keysA.every((k, i) => k === keysB[i])) return false;\n return keysA.every((k) => isEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]));\n}\n\n/**\n * 单例工厂:将传入的构造函数包装为单例模式;Proxy 拦截 construct,多次 new 返回同一实例。\n * Singleton factory: wrap constructor so that multiple new calls return the same instance.\n *\n * @template T - 实例类型(需为 object)。Instance type (must be object).\n * @param className - 被包装的构造函数(class)。Constructor (class) to wrap.\n * @returns 代理后的构造函数,调用时始终返回同一实例。Proxied constructor returning same instance.\n *\n * @example\n * ```ts\n * class MyService { ... }\n * const SingletonService = singleton(MyService);\n * const a = new SingletonService();\n * const b = new SingletonService();\n * console.log(a === b); // true\n * ```\n */\nexport const singleton = <T extends object>(className: Constructor<T>): Constructor<T> => {\n let instance: T | undefined;\n let parameters: unknown[] = [];\n return new Proxy(className, {\n construct(_target: Constructor<T>, args: unknown[]): T {\n if (!instance) {\n instance = new className(...args);\n parameters = args;\n }\n if (!isEqual(args, parameters)) {\n throw new Error(\"Cannot create multiple instances with different parameters\");\n }\n return instance;\n },\n }) as Constructor<T>;\n};\n","import type { Nilable } from \"@/types\";\n\n/**\n * 若值为 null/undefined 则抛出 Promise 使 React Suspense 挂起,否则返回该值。\n * If value is null/undefined, throw a promise to suspend (React Suspense); otherwise return value.\n * @param value - 待检查的值。Value to check.\n * @param delay - 重试前的延迟(毫秒)。Delay in ms before retry.\n * @returns 非空时的 value。Value when non-null.\n * @example const data = suspenseIfNull(resource); // 在 Suspense 边界内,data 非空时才渲染\n */\nexport function suspenseIfNull<T>(value: Nilable<T>, delay = 100): T {\n if (value == null) {\n throw new Promise<void>((resolve) => setTimeout(resolve, delay));\n }\n return value;\n}\n","/**\n * 格式化日期为 YYYY/MM/DD(空或无效返回 \"\")\n * Format date as YYYY/MM/DD; empty or invalid returns \"\".\n * @param dateString - 日期字符串\n * @returns 格式化后的日期字符串,无效则 \"\"\n */\nexport const formatDisplayDate = (dateString: string | undefined | null): string => {\n if (!dateString) return \"\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return \"\";\n const year = date.getFullYear();\n const month = (date.getMonth() + 1).toString().padStart(2, \"0\");\n const day = date.getDate().toString().padStart(2, \"0\");\n return `${year}/${month}/${day}`;\n};\n\n/**\n * 格式化为相对时间(如 2 days ago, 3 months ago)\n * Format as relative time (e.g. 2 days ago, 3 months ago).\n * @param dateString - 日期字符串\n * @returns 相对时间字符串,无效为 \"Invalid date\",空为 \"Unknown\"\n */\nexport const formatRelativeTime = (dateString: string | undefined | null): string => {\n if (!dateString) return \"Unknown\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return \"Invalid date\";\n\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffSeconds = Math.floor(diffMs / 1000);\n const diffMinutes = Math.floor(diffSeconds / 60);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n const diffMonths = Math.floor(diffDays / 30);\n const diffYears = Math.floor(diffDays / 365);\n\n if (diffYears > 0) return `${diffYears} year${diffYears > 1 ? \"s\" : \"\"} ago`;\n if (diffMonths > 0) return `${diffMonths} month${diffMonths > 1 ? \"s\" : \"\"} ago`;\n if (diffDays > 0) return `${diffDays} day${diffDays > 1 ? \"s\" : \"\"} ago`;\n if (diffHours > 0) return `${diffHours} hour${diffHours > 1 ? \"s\" : \"\"} ago`;\n if (diffMinutes > 0) return `${diffMinutes} minute${diffMinutes > 1 ? \"s\" : \"\"} ago`;\n return \"Just now\";\n};\n\n/**\n * 格式化 ISO 日期为图表轴/提示用短格式(如:Jan 4, 02:30 PM)\n * Format ISO date for chart axis/tooltip (e.g. Jan 4, 02:30 PM).\n * @param iso - ISO 日期字符串\n * @returns 格式化字符串,解析失败则返回原串\n */\nexport function formatTickDate(iso: string): string {\n try {\n const d = new Date(iso);\n return d.toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n } catch {\n return iso;\n }\n}\n\n/**\n * 格式化时间显示年月日时分 (YYYY/MM/DD HH:mm)\n * Format date-time as year/month/day hour:minute.\n * @param dateString - 时间字符串\n * @returns 格式化后的时间字符串,无效则返回原串\n */\nexport const formatDateTime = (dateString: string): string => {\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return dateString;\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const hour = String(date.getHours()).padStart(2, \"0\");\n const minute = String(date.getMinutes()).padStart(2, \"0\");\n return `${year}/${month}/${day} ${hour}:${minute}`;\n } catch {\n return dateString;\n }\n};\n\n/**\n * 格式化时间显示月日时分 (MM/DD HH:mm)\n * Format date-time as month/day hour:minute.\n * @param dateString - 时间字符串\n * @returns 格式化后的时间字符串,无效则返回原串\n */\nexport const formatMonthDayTime = (dateString: string): string => {\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return dateString;\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const hour = String(date.getHours()).padStart(2, \"0\");\n const minute = String(date.getMinutes()).padStart(2, \"0\");\n return `${month}/${day} ${hour}:${minute}`;\n } catch {\n return dateString;\n }\n};\n\n/**\n * 格式化日期仅年月日 (YYYY/MM/DD),用于生日等\n * Format date as year/month/day only.\n * @param dateString - 日期字符串,如 \"2000-01-15\" 或 ISO\n * @returns 格式化后的日期字符串,无效则返回原串\n */\nexport const formatDate = (dateString: string): string => {\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return dateString;\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}/${month}/${day}`;\n } catch {\n return dateString;\n }\n};\n\n/**\n * 格式化时间显示时分 (HH:mm)\n * Format time as hour:minute.\n * @param dateString - 时间字符串\n * @returns 格式化后的时间字符串,无效则返回原串\n */\nexport const formatTime = (dateString: string): string => {\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) return dateString;\n const hour = String(date.getHours()).padStart(2, \"0\");\n const minute = String(date.getMinutes()).padStart(2, \"0\");\n return `${hour}:${minute}`;\n } catch {\n return dateString;\n }\n};\n","/**\n * Random utilities (no color logic).\n * 用于项目的随机数/随机选择工具(不包含颜色选择,颜色相关请看 `utils/colors.ts`)。\n */\n\n/**\n * Generate a floating number in range [min, max).\n * 生成区间为 [min, max) 的浮点数。\n */\nexport const randomBetween = (min: number, max: number): number => {\n return min + Math.random() * (max - min);\n};\n\n/**\n * Generate a random integer in range [min, maxInclusive].\n * 生成区间为 [min, maxInclusive] 的整数。\n */\nexport const randomInt = (minInclusive: number, maxInclusive: number): number => {\n if (maxInclusive < minInclusive) {\n // Fail fast to prevent silent bugs.\n throw new Error(\"randomInt: maxInclusive must be >= minInclusive\");\n }\n const span = maxInclusive - minInclusive + 1;\n return minInclusive + Math.floor(Math.random() * span);\n};\n\n/**\n * Randomly return a boolean.\n * @param probability True probability in range [0, 1] (default 0.5).\n */\nexport const randomBool = (probability = 0.5): boolean => {\n if (probability < 0 || probability > 1) {\n throw new Error(\"randomBool: probability must be within [0, 1]\");\n }\n return Math.random() < probability;\n};\n\n/**\n * Alias of `randomBool` (chance).\n * @param probability True probability in range [0, 1] (default 0.5).\n */\nexport const chance = (probability = 0.5): boolean => {\n return randomBool(probability);\n};\n\n/**\n * Randomly return either -1 or 1.\n */\nexport const randomSign = (): number => {\n return Math.random() < 0.5 ? -1 : 1;\n};\n\n/**\n * Randomly pick one element from a pool.\n * 从数组/只读数组中随机取一个元素。\n */\nexport const pickRandom = <T>(pool: readonly T[]): T => {\n if (pool.length === 0) throw new Error(\"pickRandom: empty pool\");\n return pool[Math.floor(Math.random() * pool.length)];\n};\n\n/**\n * Generate a Gaussian (normal) distributed number using Box-Muller.\n * @param mean Mean of the normal distribution.\n * @param stdDev Standard deviation (must be > 0).\n */\nexport const randomGaussian = (mean = 0, stdDev = 1): number => {\n if (stdDev <= 0) throw new Error(\"randomGaussian: stdDev must be > 0\");\n // Box-Muller transform.\n let u = 0;\n let v = 0;\n while (u === 0) u = Math.random();\n while (v === 0) v = Math.random();\n const mag = Math.sqrt(-2.0 * Math.log(u));\n const z0 = mag * Math.cos(2.0 * Math.PI * v);\n return z0 * stdDev + mean;\n};\n\n","/**\n * 从字符串列表创建键值映射对象;可选 valueMapper 指定值\n * Create record from string list; optional valueMapper for values.\n * @param list - 字符串数组(作为键)\n * @param valueMapper - 可选,(item, index) => value,缺省时值为键本身\n * @returns Record<list[number], V>\n * @example createMap([\"a\",\"b\",\"c\"]) // { a: \"a\", b: \"b\", c: \"c\" }\n * @example createMap([\"a\",\"b\",\"c\"], (_, i) => i) // { a: 0, b: 1, c: 2 }\n */\nexport function createMap<T extends string, V = T>(list: ReadonlyArray<T>, valueMapper?: (item: T, index: number) => V): Record<T, V> {\n return list.reduce(\n (acc, item, index) => {\n acc[item] = valueMapper ? valueMapper(item, index) : (item as unknown as V);\n return acc;\n },\n {} as Record<T, V>,\n );\n}\n"],"mappings":"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"}
|