@wwog/react 1.2.5 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/index.d.mts +45 -43
- package/dist/index.js +1 -1
- package/package.json +5 -4
- package/src/components/ProcessControl/Switch.tsx +2 -2
- package/src/components/ProcessControl/index.ts +4 -4
- package/src/components/Struct/index.ts +2 -2
- package/src/components/Sundry/ClassName.tsx +1 -1
- package/src/components/Sundry/index.ts +4 -4
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useControlled.ts +22 -27
- package/src/index.ts +9 -5
- package/src/utils/cx.test.ts +33 -0
- package/src/utils/cx.ts +25 -0
- package/src/utils/reactUtils.ts +22 -0
- package/src/utils/sundry.test.ts +130 -0
- package/src/utils/sundry.ts +130 -0
- package/src/utils/index.ts +0 -185
package/README.md
CHANGED
|
@@ -314,7 +314,7 @@ function Layout() {
|
|
|
314
314
|
|
|
315
315
|
#### `<ClassName>` (v1.2.5+)
|
|
316
316
|
|
|
317
|
-
用于将 CSS 类名分类编写的组件,内置类似`clsx`的功能,并且可以去除重复的 className
|
|
317
|
+
用于将 CSS 类名分类编写的组件,内置类似`clsx`的功能,并且可以去除重复的 className。
|
|
318
318
|
|
|
319
319
|
```tsx
|
|
320
320
|
import { ClassName } from "@wwog/react";
|
|
@@ -327,6 +327,7 @@ function Example() {
|
|
|
327
327
|
hover: "hover:bg-gray-100",
|
|
328
328
|
active: "active:bg-gray-200",
|
|
329
329
|
focus: "focus:ring-2",
|
|
330
|
+
other: "button",
|
|
330
331
|
}}
|
|
331
332
|
>
|
|
332
333
|
<button>点击我</button>
|
package/dist/index.d.mts
CHANGED
|
@@ -259,49 +259,6 @@ interface ScopeProps {
|
|
|
259
259
|
*/
|
|
260
260
|
declare const Scope: FC<ScopeProps>;
|
|
261
261
|
|
|
262
|
-
/**
|
|
263
|
-
* @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
|
|
264
|
-
* @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
|
|
265
|
-
*/
|
|
266
|
-
declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
|
|
267
|
-
/**
|
|
268
|
-
* @param schema
|
|
269
|
-
* @example
|
|
270
|
-
* YY | 18 | Two-digit year
|
|
271
|
-
* YYYY | 2018 | Four-digit year
|
|
272
|
-
* M | 1-12 | The month, beginning at 1
|
|
273
|
-
* MM | 01-12 | The month, 2-digits
|
|
274
|
-
* MMM | Jan-Dec | The abbreviated month name
|
|
275
|
-
* MMMM | January-December | The full month name
|
|
276
|
-
* D | 1-31 | The day of the month
|
|
277
|
-
* DD | 01-31 | The day of the month, 2-digits
|
|
278
|
-
* d | 0-6 | The day of the week, with Sunday as 0
|
|
279
|
-
* dd | Su-Sa | The min name of the day of the week
|
|
280
|
-
* ddd | Sun-Sat | The short name of the day of the week
|
|
281
|
-
* dddd | Sunday-Saturday | The name of the day of the week
|
|
282
|
-
* H | 0-23 | The hour
|
|
283
|
-
* HH | 00-23 | The hour, 2-digits
|
|
284
|
-
* h | 1-12 | The hour, 12-hour clock
|
|
285
|
-
* hh | 01-12 | The hour, 12-hour clock, 2-digits
|
|
286
|
-
* m | 0-59 | The minute
|
|
287
|
-
* mm | 00-59 | The minute, 2-digits
|
|
288
|
-
* s | 0-59 | The second
|
|
289
|
-
* ss | 00-59 | The second, 2-digits
|
|
290
|
-
* SSS | 000-999 | The millisecond, 3-digits
|
|
291
|
-
* Z | +05:00 | The offset from UTC, ±HH:mm
|
|
292
|
-
* ZZ | +0500 | The offset from UTC, ±HHmm
|
|
293
|
-
* A | AM | PM
|
|
294
|
-
* a | am | pm
|
|
295
|
-
*/
|
|
296
|
-
declare function formatDate(schema: string, date?: Date): string;
|
|
297
|
-
declare class Counter {
|
|
298
|
-
count: number;
|
|
299
|
-
/**
|
|
300
|
-
* @description 获取下一个计数值,不考虑越界。
|
|
301
|
-
* @description_en Get the next count value, without considering overflow.
|
|
302
|
-
*/
|
|
303
|
-
next(): number;
|
|
304
|
-
}
|
|
305
262
|
type CxInput = string | string[] | Record<string, boolean> | undefined | null | false;
|
|
306
263
|
declare function cx(...args: CxInput[]): string;
|
|
307
264
|
|
|
@@ -464,5 +421,50 @@ interface UseControlledOptions<T> {
|
|
|
464
421
|
}
|
|
465
422
|
declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
|
|
466
423
|
|
|
424
|
+
/**
|
|
425
|
+
* @param schema
|
|
426
|
+
* @example
|
|
427
|
+
* YY | 18 | Two-digit year
|
|
428
|
+
* YYYY | 2018 | Four-digit year
|
|
429
|
+
* M | 1-12 | The month, beginning at 1
|
|
430
|
+
* MM | 01-12 | The month, 2-digits
|
|
431
|
+
* MMM | Jan-Dec | The abbreviated month name
|
|
432
|
+
* MMMM | January-December | The full month name
|
|
433
|
+
* D | 1-31 | The day of the month
|
|
434
|
+
* DD | 01-31 | The day of the month, 2-digits
|
|
435
|
+
* d | 0-6 | The day of the week, with Sunday as 0
|
|
436
|
+
* dd | Su-Sa | The min name of the day of the week
|
|
437
|
+
* ddd | Sun-Sat | The short name of the day of the week
|
|
438
|
+
* dddd | Sunday-Saturday | The name of the day of the week
|
|
439
|
+
* H | 0-23 | The hour
|
|
440
|
+
* HH | 00-23 | The hour, 2-digits
|
|
441
|
+
* h | 1-12 | The hour, 12-hour clock
|
|
442
|
+
* hh | 01-12 | The hour, 12-hour clock, 2-digits
|
|
443
|
+
* m | 0-59 | The minute
|
|
444
|
+
* mm | 00-59 | The minute, 2-digits
|
|
445
|
+
* s | 0-59 | The second
|
|
446
|
+
* ss | 00-59 | The second, 2-digits
|
|
447
|
+
* SSS | 000-999 | The millisecond, 3-digits
|
|
448
|
+
* Z | +05:00 | The offset from UTC, ±HH:mm
|
|
449
|
+
* ZZ | +0500 | The offset from UTC, ±HHmm
|
|
450
|
+
* A | AM | PM
|
|
451
|
+
* a | am | pm
|
|
452
|
+
*/
|
|
453
|
+
declare function formatDate(schema: string, date?: Date): string;
|
|
454
|
+
declare class Counter {
|
|
455
|
+
count: number;
|
|
456
|
+
/**
|
|
457
|
+
* @description 获取下一个计数值,不考虑越界。
|
|
458
|
+
* @description_en Get the next count value, without considering overflow.
|
|
459
|
+
*/
|
|
460
|
+
next(): number;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
|
|
465
|
+
* @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
|
|
466
|
+
*/
|
|
467
|
+
declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
|
|
468
|
+
|
|
467
469
|
export { ArrayRender, ClassName, Counter, DateRender, False, If, Pipe, Scope, SizeBox, Switch, Toggle, True, When, childrenLoop, cx, formatDate, useControlled };
|
|
468
470
|
export type { ArrayRenderProps, ClassNameProps, CxInput, DateRenderProps, ElseIfProps, ElseProps, FalseProps, IfProps, PipeProps, ScopeProps, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import r,{useMemo as h,useEffect as
|
|
1
|
+
import r,{useMemo as h,useEffect as C,isValidElement as I,Fragment as M,cloneElement as R,useState as T,useCallback as H}from"react";function A(n,t){if(n===void 0)return;let e=0;if(Array.isArray(n)){for(const l of n)if(t(l,e++)===!1)break}else t(n,e)}const P=(n,t)=>n===t,S=n=>r.createElement(r.Fragment,null,n.children);S.displayName="Switch_Case";const E=n=>r.createElement(r.Fragment,null,n.children);E.displayName="Switch_Default";const g=n=>{const{value:t,compare:e=P,children:l,strict:a=!1}=n,o=new Set;let s=null,c=null,u=!1;return A(l,(d,i)=>{if(!r.isValidElement(d))throw new Error(`Switch Children only accepts valid React elements at index ${i}`);const f=d.type;if(f.displayName===S.displayName){const m=d.props;if(o.has(m.value))throw new Error(`Switch found duplicate Case value at index ${i}: ${JSON.stringify(m.value)}${a?" (detected in strict mode)":""}`);if(o.add(m.value),!s&&e(t,m.value)&&(s=m.children,a===!1))return!1}else if(f.displayName===E.displayName){if(u)throw new Error(`Switch can only have one Default child at index ${i}`);if(u=!0,c=d.props.children,!a&&s)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(f.displayName||f.name||f)} at index ${i}`)}),r.createElement(r.Fragment,null,s??c)};g.displayName="Switch",g.Case=S,g.Default=E,g.createTyped=function(){return{Switch:g,Case:S,Default:E}};const w=n=>r.createElement(r.Fragment,null,n.children),N=({children:n})=>r.createElement(r.Fragment,null,n),F=n=>r.createElement(r.Fragment,null,n.children);w.displayName="If_Then",N.displayName="If_Else",F.displayName="If_ElseIf";const p=({condition:n,children:t})=>{let e=null,l=null;const a=[];if(r.Children.forEach(t,o=>{if(!r.isValidElement(o))throw new Error("If component only accepts valid React elements");const s=o.type;if(s.displayName===w.displayName){if(e)throw new Error("If component can only have one Then child");e=o}else if(s.displayName===F.displayName)a.push(o);else if(s.displayName===N.displayName){if(l)throw new Error("If component can only have one Else child");l=o}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(s.displayName||s.name||s)}`)}),n)return e?r.createElement(r.Fragment,null,e.props.children):null;for(const o of a)if(o.props.condition)return r.createElement(r.Fragment,null,o.props.children);return l?r.createElement(r.Fragment,null,l.props.children):null};p.displayName="If",p.Then=w,p.ElseIf=F,p.Else=N,p.createTyped=function(){return{If:p,Then:w,ElseIf:F,Else:N}};const W=({condition:n,children:t})=>n?r.createElement(r.Fragment,null,t):null,_=({condition:n,children:t})=>n===!1?r.createElement(r.Fragment,null,t):null,B=({all:n,any:t,none:e,children:l,fallback:a})=>h(()=>(n&&(t||e)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(n&&n.length>0&&n.every(Boolean)||t&&t.length>0&&t.some(Boolean)||e&&e.length>0&&e.every(o=>!o))),[n,t,e])?r.createElement(r.Fragment,null,l):r.createElement(r.Fragment,null,a||null),V=({data:n,transform:t,render:e,fallback:l})=>{const a=h(()=>t.reduce((o,s)=>s(o),n),[n,t]);return a==null?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,e(a))},Z=n=>{const{children:t,h:e,w:l,size:a,height:o,width:s,className:c}=n;return r.createElement("div",{style:{width:a||l||s,height:a||e||o,flexShrink:0},className:c},t)},$=({let:n,props:t,children:e,fallback:l})=>{const a=h(()=>typeof n=="function"?n(t):n,[n,t]);return!e||!Object.keys(a).length?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,e(a))};function v(...n){const t=new Set;for(const e of n)if(e){if(typeof e=="string")t.add(e);else if(Array.isArray(e))e.forEach(l=>t.add(l));else if(typeof e=="object")for(const[l,a]of Object.entries(e))a&&t.add(l)}return Array.from(t).join(" ")}const z=n=>{const{className:t,children:e,asWrapper:l}=n;if(C(()=>{I(e)===!1&&console.warn("<ClassName>: children is not a valid React element. Please check your code.")},[e]),!e)return null;if(!t)return r.createElement(M,null,e);const a=v(...Object.values(t));return l?r.createElement(typeof l=="string"?l:"div",{className:a},e):I(e)?R(e,{className:v(e.props.className,a)}):r.createElement(M,null,e)},L=n=>{const{index:t=0,options:e,next:l,render:a}=n;C(()=>{if(e.length<t+1)throw new Error(`Index ${t} is out of bounds for options array of length ${e.length}. Defaulting to first option.`)},[t,e]);const[o,s]=T(t),c=()=>{s(u=>e.length?l?l(u,e):(u+1)%e.length:u)};return a(e[o],c)};function q(n){const{items:t,renderItem:e,filter:l}=n;return t?r.createElement(M,null,t.map((a,o)=>l&&!l(a)?null:e(a,o))):(console.error("ArrayRender: items is null"),null)}function G({source:n,format:t,children:e}){const l=h(()=>{if(n instanceof Date)return n;if(typeof n=="string"||typeof n=="number"){const o=new Date(n);return isNaN(o.getTime())?null:o}return null},[n]),a=h(()=>l?t?t(l):l.toLocaleString():null,[l,t]);return!a||!e?null:r.createElement(r.Fragment,null,e(a))}const K="onChange",Q="value";function U(n){const{defaultValue:t,onBeforeChange:e,trigger:l=K,valuePropName:a=Q,props:o}=n,s=Object.prototype.hasOwnProperty.call(o,a),[c,u]=T(t),d=s?o[a]:c,i=h(()=>o[l],[o,l]),f=H(m=>{const y=typeof m=="function"?m(d):m;e&&e(y,d)===!1||(s||u(y),i&&i(y))},[s,e,d,i]);return[d,f]}function X(n,t){const e=t||new Date,l=e.getFullYear(),a=e.getMonth()+1,o=e.getDate(),s=e.getHours(),c=e.getMinutes(),u=e.getSeconds(),d=e.getMilliseconds(),i=e.getDay(),f=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],m=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],y=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],Y=["January","February","March","April","May","June","July","August","September","October","November","December"],x=m[i],D=f[i],b=a-1,k=Y[b],O=y[b],J={YY:l.toString().slice(2),YYYY:l.toString(),M:a.toString(),MM:a.toString().padStart(2,"0"),MMM:O,MMMM:k,D:o.toString(),DD:o.toString().padStart(2,"0"),d:i.toString(),dd:D,ddd:D,dddd:x,H:s.toString(),HH:s.toString().padStart(2,"0"),h:(s%12).toString(),hh:(s%12).toString().padStart(2,"0"),m:c.toString(),mm:c.toString().padStart(2,"0"),s:u.toString(),ss:u.toString().padStart(2,"0"),SSS:d.toString().padStart(3,"0"),Z:"+08:00",ZZ:"+0800",A:s<12?"AM":"PM",a:s<12?"am":"pm"};return n.replace(/YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|SSS|Z{1,2}|A|a/g,j=>J[j])}class ee{count=0;next(){return this.count++}}export{q as ArrayRender,z as ClassName,ee as Counter,G as DateRender,_ as False,p as If,V as Pipe,$ as Scope,Z as SizeBox,g as Switch,L as Toggle,W as True,B as When,A as childrenLoop,v as cx,X as formatDate,U as useControlled};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wwog/react",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "wwog",
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "unbuild",
|
|
19
19
|
"format": "biome format --write src",
|
|
20
|
-
"check": "biome check --
|
|
21
|
-
"test": "
|
|
22
|
-
"test:types": "tsc --noEmit --skipLibCheck"
|
|
20
|
+
"check": "biome check --write src",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:types": "tsc --noEmit --skipLibCheck",
|
|
23
|
+
"all-suites": "pnpm run format && pnpm run check && pnpm run test:types && pnpm run test"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"@biomejs/biome": "^1.9.4",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { childrenLoop } from "../../utils";
|
|
2
|
+
import { childrenLoop } from "../../utils/reactUtils";
|
|
3
3
|
|
|
4
4
|
export interface SwitchProps<T> {
|
|
5
5
|
value: T;
|
|
@@ -120,5 +120,5 @@ Switch.createTyped = function <T>() {
|
|
|
120
120
|
Switch: (props: SwitchProps<T>) => React.ReactElement | null;
|
|
121
121
|
Case: (props: SwitchCaseProps<T>) => React.ReactElement;
|
|
122
122
|
Default: (props: SwitchDefaultProps) => React.ReactElement;
|
|
123
|
-
}
|
|
123
|
+
};
|
|
124
124
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from './Switch'
|
|
2
|
+
export * from './If'
|
|
3
|
+
export * from './When'
|
|
4
|
+
export * from './Pipe'
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from './ArrayRender'
|
|
2
|
+
export * from './DateRender'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from './SizeBox'
|
|
2
|
+
export * from './Scope'
|
|
3
|
+
export * from './ClassName'
|
|
4
|
+
export * from './Toggle'
|
package/src/hooks/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from './useControlled'
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState
|
|
1
|
+
import {type Dispatch, useCallback, useMemo, useState} from 'react'
|
|
2
2
|
|
|
3
|
-
const DEFAULT_TRIGGER =
|
|
4
|
-
const DEFAULT_VALUE_PROP_NAME =
|
|
3
|
+
const DEFAULT_TRIGGER = 'onChange'
|
|
4
|
+
const DEFAULT_VALUE_PROP_NAME = 'value'
|
|
5
5
|
|
|
6
6
|
export interface UseControlledOptions<T> {
|
|
7
7
|
/**
|
|
8
8
|
* @description - 非受控模式下的默认值,会被受控模式下的值覆盖
|
|
9
9
|
* @description_en - Default value in uncontrolled mode, will be overridden by the value in controlled mode
|
|
10
10
|
*/
|
|
11
|
-
defaultValue: T
|
|
11
|
+
defaultValue: T
|
|
12
12
|
/**
|
|
13
13
|
* @description - 值变更前的回调函数,可用于拦截或修改新值
|
|
14
14
|
* @description_en - Callback function before the value changes, can be used to intercept or modify the new value
|
|
15
15
|
*/
|
|
16
|
-
onBeforeChange?: (newValue: T, currentValue: T) => boolean | void
|
|
16
|
+
onBeforeChange?: (newValue: T, currentValue: T) => boolean | void
|
|
17
17
|
/**
|
|
18
18
|
* @description - 当值发生变化时触发的回调函数名
|
|
19
19
|
* @description_en - Callback function name triggered when the value changes
|
|
20
20
|
* @default - onChange
|
|
21
21
|
*/
|
|
22
|
-
trigger?: string
|
|
22
|
+
trigger?: string
|
|
23
23
|
/**
|
|
24
24
|
* @description - 值的属性名
|
|
25
25
|
* @description_en - Property name of the value
|
|
26
26
|
* @default - value
|
|
27
27
|
*/
|
|
28
|
-
valuePropName?: string
|
|
29
|
-
props: Record<string, any
|
|
28
|
+
valuePropName?: string
|
|
29
|
+
props: Record<string, any>
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function useControlled<T>(
|
|
33
|
-
options: UseControlledOptions<T
|
|
33
|
+
options: UseControlledOptions<T>,
|
|
34
34
|
): [T, Dispatch<React.SetStateAction<T>>] {
|
|
35
35
|
const {
|
|
36
36
|
defaultValue,
|
|
@@ -38,36 +38,31 @@ export function useControlled<T>(
|
|
|
38
38
|
trigger = DEFAULT_TRIGGER,
|
|
39
39
|
valuePropName = DEFAULT_VALUE_PROP_NAME,
|
|
40
40
|
props,
|
|
41
|
-
} = options
|
|
42
|
-
const isControlled = Object.prototype.hasOwnProperty.call(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
const [internalValue, setInternalValue] = useState<T>(defaultValue);
|
|
47
|
-
const value = isControlled ? props[valuePropName] : internalValue;
|
|
48
|
-
const onChange = useMemo(() => props[trigger], [props, trigger]);
|
|
41
|
+
} = options
|
|
42
|
+
const isControlled = Object.prototype.hasOwnProperty.call(props, valuePropName)
|
|
43
|
+
const [internalValue, setInternalValue] = useState<T>(defaultValue)
|
|
44
|
+
const value = isControlled ? props[valuePropName] : internalValue
|
|
45
|
+
const onChange = useMemo(() => props[trigger], [props, trigger])
|
|
49
46
|
|
|
50
47
|
const setValue = useCallback<Dispatch<React.SetStateAction<T>>>(
|
|
51
48
|
(newValue) => {
|
|
52
49
|
const resolvedValue =
|
|
53
|
-
typeof newValue ===
|
|
54
|
-
? (newValue as (prev: T) => T)(value)
|
|
55
|
-
: newValue;
|
|
50
|
+
typeof newValue === 'function' ? (newValue as (prev: T) => T)(value) : newValue
|
|
56
51
|
if (onBeforeChange) {
|
|
57
|
-
const shouldProceed = onBeforeChange(resolvedValue, value)
|
|
52
|
+
const shouldProceed = onBeforeChange(resolvedValue, value)
|
|
58
53
|
if (shouldProceed === false) {
|
|
59
|
-
return
|
|
54
|
+
return
|
|
60
55
|
}
|
|
61
56
|
}
|
|
62
57
|
if (!isControlled) {
|
|
63
|
-
setInternalValue(resolvedValue)
|
|
58
|
+
setInternalValue(resolvedValue)
|
|
64
59
|
}
|
|
65
60
|
if (onChange) {
|
|
66
|
-
onChange(resolvedValue)
|
|
61
|
+
onChange(resolvedValue)
|
|
67
62
|
}
|
|
68
63
|
},
|
|
69
|
-
[isControlled, onBeforeChange,
|
|
70
|
-
)
|
|
64
|
+
[isControlled, onBeforeChange, value, onChange],
|
|
65
|
+
)
|
|
71
66
|
|
|
72
|
-
return [value, setValue]
|
|
67
|
+
return [value, setValue]
|
|
73
68
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
|
|
5
|
-
export * from
|
|
1
|
+
export * from './components/ProcessControl'
|
|
2
|
+
export * from './components/Sundry'
|
|
3
|
+
export * from './components/Struct'
|
|
4
|
+
|
|
5
|
+
export * from './hooks'
|
|
6
|
+
|
|
7
|
+
export * from './utils/sundry'
|
|
8
|
+
export * from './utils/cx'
|
|
9
|
+
export * from './utils/reactUtils'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
import {cx} from './cx'
|
|
3
|
+
|
|
4
|
+
describe('cx', () => {
|
|
5
|
+
it('应该正确合并字符串类名', () => {
|
|
6
|
+
expect(cx('foo', 'bar')).toBe('foo bar')
|
|
7
|
+
expect(cx('foo', 'bar', 'baz')).toBe('foo bar baz')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('应该过滤掉 falsy 值', () => {
|
|
11
|
+
expect(cx('foo', null, 'bar', undefined, false)).toBe('foo bar')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('应该正确处理数组', () => {
|
|
15
|
+
expect(cx(['foo', 'bar'])).toBe('foo bar')
|
|
16
|
+
expect(cx('baz', ['foo', 'bar'])).toBe('baz foo bar')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('应该正确处理对象', () => {
|
|
20
|
+
expect(cx({foo: true, bar: false})).toBe('foo')
|
|
21
|
+
expect(cx({foo: true, bar: true})).toBe('foo bar')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('应该正确处理混合输入', () => {
|
|
25
|
+
expect(cx('foo', ['bar', 'baz'], {qux: true, quux: false})).toBe('foo bar baz qux')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('应该去除重复的类名', () => {
|
|
29
|
+
expect(cx('foo', 'foo', 'bar')).toBe('foo bar')
|
|
30
|
+
expect(cx('foo', ['foo', 'bar'])).toBe('foo bar')
|
|
31
|
+
expect(cx('foo', {foo: true})).toBe('foo')
|
|
32
|
+
})
|
|
33
|
+
})
|
package/src/utils/cx.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type CxInput = string | string[] | Record<string, boolean> | undefined | null | false
|
|
2
|
+
|
|
3
|
+
export function cx(...args: CxInput[]): string {
|
|
4
|
+
const classes = new Set<string>()
|
|
5
|
+
|
|
6
|
+
for (const arg of args) {
|
|
7
|
+
if (!arg) {
|
|
8
|
+
continue
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (typeof arg === 'string') {
|
|
12
|
+
classes.add(arg)
|
|
13
|
+
} else if (Array.isArray(arg)) {
|
|
14
|
+
arg.forEach((item) => classes.add(item))
|
|
15
|
+
} else if (typeof arg === 'object') {
|
|
16
|
+
for (const [key, value] of Object.entries(arg)) {
|
|
17
|
+
if (value) {
|
|
18
|
+
classes.add(key)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Array.from(classes).join(' ')
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
/**
|
|
3
|
+
* @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
|
|
4
|
+
* @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
|
|
5
|
+
*/
|
|
6
|
+
export function childrenLoop(
|
|
7
|
+
children: React.ReactNode | undefined,
|
|
8
|
+
callback: (child: React.ReactNode, index: number) => boolean | void,
|
|
9
|
+
): void {
|
|
10
|
+
if (children === undefined) return
|
|
11
|
+
let index = 0
|
|
12
|
+
if (Array.isArray(children)) {
|
|
13
|
+
for (const child of children) {
|
|
14
|
+
const shouldContinue = callback(child, index++)
|
|
15
|
+
if (shouldContinue === false) {
|
|
16
|
+
break
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
callback(children, index)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
import {Counter, formatDate} from './sundry'
|
|
3
|
+
|
|
4
|
+
describe('formatDate', () => {
|
|
5
|
+
// 使用一个固定的日期来测试,以避免时间差异导致的测试失败
|
|
6
|
+
// 2023-04-15 14:30:45.678 星期六
|
|
7
|
+
const testDate = new Date(2023, 3, 15, 14, 30, 45, 678)
|
|
8
|
+
|
|
9
|
+
it('应该正确格式化年份', () => {
|
|
10
|
+
expect(formatDate('YY', testDate)).toBe('23')
|
|
11
|
+
expect(formatDate('YYYY', testDate)).toBe('2023')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('应该正确格式化月份', () => {
|
|
15
|
+
expect(formatDate('M', testDate)).toBe('4')
|
|
16
|
+
expect(formatDate('MM', testDate)).toBe('04')
|
|
17
|
+
expect(formatDate('MMM', testDate)).toBe('Apr')
|
|
18
|
+
expect(formatDate('MMMM', testDate)).toBe('April')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('应该正确格式化日期', () => {
|
|
22
|
+
expect(formatDate('D', testDate)).toBe('15')
|
|
23
|
+
expect(formatDate('DD', testDate)).toBe('15')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('应该正确格式化星期', () => {
|
|
27
|
+
expect(formatDate('d', testDate)).toBe('6')
|
|
28
|
+
expect(formatDate('dd', testDate)).toBe('Sat')
|
|
29
|
+
expect(formatDate('ddd', testDate)).toBe('Sat')
|
|
30
|
+
expect(formatDate('dddd', testDate)).toBe('Saturday')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('应该正确格式化小时', () => {
|
|
34
|
+
expect(formatDate('H', testDate)).toBe('14')
|
|
35
|
+
expect(formatDate('HH', testDate)).toBe('14')
|
|
36
|
+
expect(formatDate('h', testDate)).toBe('2')
|
|
37
|
+
expect(formatDate('hh', testDate)).toBe('02')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('应该正确格式化分钟和秒', () => {
|
|
41
|
+
expect(formatDate('m', testDate)).toBe('30')
|
|
42
|
+
expect(formatDate('mm', testDate)).toBe('30')
|
|
43
|
+
expect(formatDate('s', testDate)).toBe('45')
|
|
44
|
+
expect(formatDate('ss', testDate)).toBe('45')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('应该正确格式化毫秒', () => {
|
|
48
|
+
expect(formatDate('SSS', testDate)).toBe('678')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('应该正确格式化上午/下午', () => {
|
|
52
|
+
expect(formatDate('A', testDate)).toBe('PM')
|
|
53
|
+
expect(formatDate('a', testDate)).toBe('pm')
|
|
54
|
+
|
|
55
|
+
const morningDate = new Date(2023, 3, 15, 9, 30, 45, 678)
|
|
56
|
+
expect(formatDate('A', morningDate)).toBe('AM')
|
|
57
|
+
expect(formatDate('a', morningDate)).toBe('am')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('应该正确组合多种格式', () => {
|
|
61
|
+
expect(formatDate('YYYY-MM-DD', testDate)).toBe('2023-04-15')
|
|
62
|
+
expect(formatDate('YYYY/MM/DD HH:mm:ss', testDate)).toBe('2023/04/15 14:30:45')
|
|
63
|
+
expect(formatDate('YYYY年MM月DD日 HH时mm分ss秒', testDate)).toBe('2023年04月15日 14时30分45秒')
|
|
64
|
+
expect(formatDate('YY-MM-DD hh:mm:ss A', testDate)).toBe('23-04-15 02:30:45 PM')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('当不传入日期时应该使用当前日期', () => {
|
|
68
|
+
// 由于测试时间不确定,这里只测试格式是否正确,不测试具体的值
|
|
69
|
+
const result = formatDate('YYYY-MM-DD')
|
|
70
|
+
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('Counter', () => {
|
|
75
|
+
it('应该从0开始计数', () => {
|
|
76
|
+
const counter = new Counter()
|
|
77
|
+
expect(counter.count).toBe(0)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('next() 方法应该返回当前计数并递增', () => {
|
|
81
|
+
const counter = new Counter()
|
|
82
|
+
expect(counter.next()).toBe(0)
|
|
83
|
+
expect(counter.count).toBe(1)
|
|
84
|
+
expect(counter.next()).toBe(1)
|
|
85
|
+
expect(counter.count).toBe(2)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('连续调用 next() 应该正确递增', () => {
|
|
89
|
+
const counter = new Counter()
|
|
90
|
+
expect(counter.next()).toBe(0)
|
|
91
|
+
expect(counter.next()).toBe(1)
|
|
92
|
+
expect(counter.next()).toBe(2)
|
|
93
|
+
expect(counter.next()).toBe(3)
|
|
94
|
+
expect(counter.count).toBe(4)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
/* describe("cx", () => {
|
|
99
|
+
it("应该正确合并字符串类名", () => {
|
|
100
|
+
expect(cx("foo", "bar")).toBe("foo bar");
|
|
101
|
+
expect(cx("foo", "bar", "baz")).toBe("foo bar baz");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("应该过滤掉 falsy 值", () => {
|
|
105
|
+
expect(cx("foo", null, "bar", undefined, false)).toBe("foo bar");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("应该正确处理数组", () => {
|
|
109
|
+
expect(cx(["foo", "bar"])).toBe("foo bar");
|
|
110
|
+
expect(cx("baz", ["foo", "bar"])).toBe("baz foo bar");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("应该正确处理对象", () => {
|
|
114
|
+
expect(cx({ foo: true, bar: false })).toBe("foo");
|
|
115
|
+
expect(cx({ foo: true, bar: true })).toBe("foo bar");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("应该正确处理混合输入", () => {
|
|
119
|
+
expect(cx("foo", ["bar", "baz"], { qux: true, quux: false })).toBe(
|
|
120
|
+
"foo bar baz qux"
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("应该去除重复的类名", () => {
|
|
125
|
+
expect(cx("foo", "foo", "bar")).toBe("foo bar");
|
|
126
|
+
expect(cx("foo", ["foo", "bar"])).toBe("foo bar");
|
|
127
|
+
expect(cx("foo", { foo: true })).toBe("foo");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
*/
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param schema
|
|
3
|
+
* @example
|
|
4
|
+
* YY | 18 | Two-digit year
|
|
5
|
+
* YYYY | 2018 | Four-digit year
|
|
6
|
+
* M | 1-12 | The month, beginning at 1
|
|
7
|
+
* MM | 01-12 | The month, 2-digits
|
|
8
|
+
* MMM | Jan-Dec | The abbreviated month name
|
|
9
|
+
* MMMM | January-December | The full month name
|
|
10
|
+
* D | 1-31 | The day of the month
|
|
11
|
+
* DD | 01-31 | The day of the month, 2-digits
|
|
12
|
+
* d | 0-6 | The day of the week, with Sunday as 0
|
|
13
|
+
* dd | Su-Sa | The min name of the day of the week
|
|
14
|
+
* ddd | Sun-Sat | The short name of the day of the week
|
|
15
|
+
* dddd | Sunday-Saturday | The name of the day of the week
|
|
16
|
+
* H | 0-23 | The hour
|
|
17
|
+
* HH | 00-23 | The hour, 2-digits
|
|
18
|
+
* h | 1-12 | The hour, 12-hour clock
|
|
19
|
+
* hh | 01-12 | The hour, 12-hour clock, 2-digits
|
|
20
|
+
* m | 0-59 | The minute
|
|
21
|
+
* mm | 00-59 | The minute, 2-digits
|
|
22
|
+
* s | 0-59 | The second
|
|
23
|
+
* ss | 00-59 | The second, 2-digits
|
|
24
|
+
* SSS | 000-999 | The millisecond, 3-digits
|
|
25
|
+
* Z | +05:00 | The offset from UTC, ±HH:mm
|
|
26
|
+
* ZZ | +0500 | The offset from UTC, ±HHmm
|
|
27
|
+
* A | AM | PM
|
|
28
|
+
* a | am | pm
|
|
29
|
+
*/
|
|
30
|
+
export function formatDate(schema: string, date?: Date): string {
|
|
31
|
+
const d = date || new Date()
|
|
32
|
+
const year = d.getFullYear()
|
|
33
|
+
const month = d.getMonth() + 1
|
|
34
|
+
const day = d.getDate()
|
|
35
|
+
const hour = d.getHours()
|
|
36
|
+
const minute = d.getMinutes()
|
|
37
|
+
const second = d.getSeconds()
|
|
38
|
+
const millisecond = d.getMilliseconds()
|
|
39
|
+
const week = d.getDay()
|
|
40
|
+
const weekName = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
41
|
+
const weekFullName = [
|
|
42
|
+
'Sunday',
|
|
43
|
+
'Monday',
|
|
44
|
+
'Tuesday',
|
|
45
|
+
'Wednesday',
|
|
46
|
+
'Thursday',
|
|
47
|
+
'Friday',
|
|
48
|
+
'Saturday',
|
|
49
|
+
]
|
|
50
|
+
const monthName = [
|
|
51
|
+
'Jan',
|
|
52
|
+
'Feb',
|
|
53
|
+
'Mar',
|
|
54
|
+
'Apr',
|
|
55
|
+
'May',
|
|
56
|
+
'Jun',
|
|
57
|
+
'Jul',
|
|
58
|
+
'Aug',
|
|
59
|
+
'Sep',
|
|
60
|
+
'Oct',
|
|
61
|
+
'Nov',
|
|
62
|
+
'Dec',
|
|
63
|
+
]
|
|
64
|
+
const monthFullName = [
|
|
65
|
+
'January',
|
|
66
|
+
'February',
|
|
67
|
+
'March',
|
|
68
|
+
'April',
|
|
69
|
+
'May',
|
|
70
|
+
'June',
|
|
71
|
+
'July',
|
|
72
|
+
'August',
|
|
73
|
+
'September',
|
|
74
|
+
'October',
|
|
75
|
+
'November',
|
|
76
|
+
'December',
|
|
77
|
+
]
|
|
78
|
+
// 直接使用 week 索引,不进行转换,因为 getDay() 已经返回了正确的星期索引 (0-6)
|
|
79
|
+
const weekFull = weekFullName[week]!
|
|
80
|
+
const weekShort = weekName[week]!
|
|
81
|
+
const monthIndex = month - 1
|
|
82
|
+
const monthFull = monthFullName[monthIndex]!
|
|
83
|
+
const monthShort = monthName[monthIndex]!
|
|
84
|
+
const map: Record<string, string> = {
|
|
85
|
+
YY: year.toString().slice(2),
|
|
86
|
+
YYYY: year.toString(),
|
|
87
|
+
M: month.toString(),
|
|
88
|
+
MM: month.toString().padStart(2, '0'),
|
|
89
|
+
MMM: monthShort,
|
|
90
|
+
MMMM: monthFull,
|
|
91
|
+
D: day.toString(),
|
|
92
|
+
DD: day.toString().padStart(2, '0'),
|
|
93
|
+
d: week.toString(),
|
|
94
|
+
dd: weekShort,
|
|
95
|
+
ddd: weekShort,
|
|
96
|
+
dddd: weekFull,
|
|
97
|
+
H: hour.toString(),
|
|
98
|
+
HH: hour.toString().padStart(2, '0'),
|
|
99
|
+
h: (hour % 12).toString(),
|
|
100
|
+
hh: (hour % 12).toString().padStart(2, '0'),
|
|
101
|
+
m: minute.toString(),
|
|
102
|
+
mm: minute.toString().padStart(2, '0'),
|
|
103
|
+
s: second.toString(),
|
|
104
|
+
ss: second.toString().padStart(2, '0'),
|
|
105
|
+
SSS: millisecond.toString().padStart(3, '0'),
|
|
106
|
+
Z: '+08:00',
|
|
107
|
+
ZZ: '+0800',
|
|
108
|
+
A: hour < 12 ? 'AM' : 'PM',
|
|
109
|
+
a: hour < 12 ? 'am' : 'pm',
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return schema.replace(
|
|
113
|
+
/YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|SSS|Z{1,2}|A|a/g,
|
|
114
|
+
(match) => {
|
|
115
|
+
return map[match]!
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class Counter {
|
|
121
|
+
count = 0
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @description 获取下一个计数值,不考虑越界。
|
|
125
|
+
* @description_en Get the next count value, without considering overflow.
|
|
126
|
+
*/
|
|
127
|
+
next() {
|
|
128
|
+
return this.count++
|
|
129
|
+
}
|
|
130
|
+
}
|
package/src/utils/index.ts
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
/**
|
|
3
|
-
* @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
|
|
4
|
-
* @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
|
|
5
|
-
*/
|
|
6
|
-
export function childrenLoop(
|
|
7
|
-
children: React.ReactNode | undefined,
|
|
8
|
-
callback: (child: React.ReactNode, index: number) => boolean | void
|
|
9
|
-
): void {
|
|
10
|
-
if (children === undefined) return;
|
|
11
|
-
let index = 0;
|
|
12
|
-
if (Array.isArray(children)) {
|
|
13
|
-
for (const child of children) {
|
|
14
|
-
const shouldContinue = callback(child, index++);
|
|
15
|
-
if (shouldContinue === false) {
|
|
16
|
-
break;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
} else {
|
|
20
|
-
callback(children, index);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @param schema
|
|
26
|
-
* @example
|
|
27
|
-
* YY | 18 | Two-digit year
|
|
28
|
-
* YYYY | 2018 | Four-digit year
|
|
29
|
-
* M | 1-12 | The month, beginning at 1
|
|
30
|
-
* MM | 01-12 | The month, 2-digits
|
|
31
|
-
* MMM | Jan-Dec | The abbreviated month name
|
|
32
|
-
* MMMM | January-December | The full month name
|
|
33
|
-
* D | 1-31 | The day of the month
|
|
34
|
-
* DD | 01-31 | The day of the month, 2-digits
|
|
35
|
-
* d | 0-6 | The day of the week, with Sunday as 0
|
|
36
|
-
* dd | Su-Sa | The min name of the day of the week
|
|
37
|
-
* ddd | Sun-Sat | The short name of the day of the week
|
|
38
|
-
* dddd | Sunday-Saturday | The name of the day of the week
|
|
39
|
-
* H | 0-23 | The hour
|
|
40
|
-
* HH | 00-23 | The hour, 2-digits
|
|
41
|
-
* h | 1-12 | The hour, 12-hour clock
|
|
42
|
-
* hh | 01-12 | The hour, 12-hour clock, 2-digits
|
|
43
|
-
* m | 0-59 | The minute
|
|
44
|
-
* mm | 00-59 | The minute, 2-digits
|
|
45
|
-
* s | 0-59 | The second
|
|
46
|
-
* ss | 00-59 | The second, 2-digits
|
|
47
|
-
* SSS | 000-999 | The millisecond, 3-digits
|
|
48
|
-
* Z | +05:00 | The offset from UTC, ±HH:mm
|
|
49
|
-
* ZZ | +0500 | The offset from UTC, ±HHmm
|
|
50
|
-
* A | AM | PM
|
|
51
|
-
* a | am | pm
|
|
52
|
-
*/
|
|
53
|
-
export function formatDate(schema: string, date?: Date): string {
|
|
54
|
-
const d = date || new Date();
|
|
55
|
-
const year = d.getFullYear();
|
|
56
|
-
const month = d.getMonth() + 1;
|
|
57
|
-
const day = d.getDate();
|
|
58
|
-
const hour = d.getHours();
|
|
59
|
-
const minute = d.getMinutes();
|
|
60
|
-
const second = d.getSeconds();
|
|
61
|
-
const millisecond = d.getMilliseconds();
|
|
62
|
-
const week = d.getDay();
|
|
63
|
-
const weekName = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
64
|
-
const weekFullName = [
|
|
65
|
-
"Sunday",
|
|
66
|
-
"Monday",
|
|
67
|
-
"Tuesday",
|
|
68
|
-
"Wednesday",
|
|
69
|
-
"Thursday",
|
|
70
|
-
"Friday",
|
|
71
|
-
"Saturday",
|
|
72
|
-
];
|
|
73
|
-
const monthName = [
|
|
74
|
-
"Jan",
|
|
75
|
-
"Feb",
|
|
76
|
-
"Mar",
|
|
77
|
-
"Apr",
|
|
78
|
-
"May",
|
|
79
|
-
"Jun",
|
|
80
|
-
"Jul",
|
|
81
|
-
"Aug",
|
|
82
|
-
"Sep",
|
|
83
|
-
"Oct",
|
|
84
|
-
"Nov",
|
|
85
|
-
"Dec",
|
|
86
|
-
];
|
|
87
|
-
const monthFullName = [
|
|
88
|
-
"January",
|
|
89
|
-
"February",
|
|
90
|
-
"March",
|
|
91
|
-
"April",
|
|
92
|
-
"May",
|
|
93
|
-
"June",
|
|
94
|
-
"July",
|
|
95
|
-
"August",
|
|
96
|
-
"September",
|
|
97
|
-
"October",
|
|
98
|
-
"November",
|
|
99
|
-
"December",
|
|
100
|
-
];
|
|
101
|
-
const weekIndex = week === 0 ? 6 : week - 1;
|
|
102
|
-
const weekFull = weekFullName[weekIndex]!;
|
|
103
|
-
const weekShort = weekName[weekIndex]!;
|
|
104
|
-
const monthIndex = month - 1;
|
|
105
|
-
const monthFull = monthFullName[monthIndex]!;
|
|
106
|
-
const monthShort = monthName[monthIndex]!;
|
|
107
|
-
const map: Record<string, string> = {
|
|
108
|
-
YY: year.toString().slice(2),
|
|
109
|
-
YYYY: year.toString(),
|
|
110
|
-
M: month.toString(),
|
|
111
|
-
MM: month.toString().padStart(2, "0"),
|
|
112
|
-
MMM: monthShort,
|
|
113
|
-
MMMM: monthFull,
|
|
114
|
-
D: day.toString(),
|
|
115
|
-
DD: day.toString().padStart(2, "0"),
|
|
116
|
-
d: week.toString(),
|
|
117
|
-
dd: weekShort,
|
|
118
|
-
ddd: weekShort,
|
|
119
|
-
dddd: weekFull,
|
|
120
|
-
H: hour.toString(),
|
|
121
|
-
HH: hour.toString().padStart(2, "0"),
|
|
122
|
-
h: (hour % 12).toString(),
|
|
123
|
-
hh: (hour % 12).toString().padStart(2, "0"),
|
|
124
|
-
m: minute.toString(),
|
|
125
|
-
mm: minute.toString().padStart(2, "0"),
|
|
126
|
-
s: second.toString(),
|
|
127
|
-
ss: second.toString().padStart(2, "0"),
|
|
128
|
-
SSS: millisecond.toString().padStart(3, "0"),
|
|
129
|
-
Z: "+08:00",
|
|
130
|
-
ZZ: "+0800",
|
|
131
|
-
A: hour < 12 ? "AM" : "PM",
|
|
132
|
-
a: hour < 12 ? "am" : "pm",
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
return schema.replace(
|
|
136
|
-
/YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|SSS|Z{1,2}|A|a/g,
|
|
137
|
-
(match) => {
|
|
138
|
-
return map[match]!;
|
|
139
|
-
}
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export class Counter {
|
|
144
|
-
count = 0;
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* @description 获取下一个计数值,不考虑越界。
|
|
148
|
-
* @description_en Get the next count value, without considering overflow.
|
|
149
|
-
*/
|
|
150
|
-
next() {
|
|
151
|
-
return this.count++;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export type CxInput =
|
|
156
|
-
| string
|
|
157
|
-
| string[]
|
|
158
|
-
| Record<string, boolean>
|
|
159
|
-
| undefined
|
|
160
|
-
| null
|
|
161
|
-
| false;
|
|
162
|
-
|
|
163
|
-
export function cx(...args: CxInput[]): string {
|
|
164
|
-
const classes = new Set<string>();
|
|
165
|
-
|
|
166
|
-
for (const arg of args) {
|
|
167
|
-
if (!arg) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (typeof arg === "string") {
|
|
172
|
-
classes.add(arg);
|
|
173
|
-
} else if (Array.isArray(arg)) {
|
|
174
|
-
arg.forEach((item) => classes.add(item));
|
|
175
|
-
} else if (typeof arg === "object") {
|
|
176
|
-
for (const [key, value] of Object.entries(arg)) {
|
|
177
|
-
if (value) {
|
|
178
|
-
classes.add(key);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return Array.from(classes).join(" ");
|
|
185
|
-
}
|