@wwog/react 1.3.11 → 1.3.13
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 +78 -0
- package/dist/index.d.mts +101 -10
- package/dist/index.js +1 -1
- package/package.json +12 -11
- package/src/algorithm/date/zellersKongruenz.ts +9 -14
- package/src/algorithm/date.ts +2 -2
- package/src/components/Layout/index.ts +2 -2
- package/src/components/ProcessControl/If.tsx +2 -2
- package/src/components/Struct/ArrayRender.tsx +14 -9
- package/src/components/Sundry/Boundary.tsx +67 -0
- package/src/components/Sundry/Portal.tsx +59 -0
- package/src/components/Sundry/Repeat.tsx +34 -0
- package/src/components/Sundry/index.ts +8 -5
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useScreen.ts +54 -65
- package/src/index.ts +5 -5
- package/src/utils/constants.ts +7 -16
- package/src/utils/createExternalState.ts +71 -84
- package/src/utils/index.ts +7 -7
- package/src/utils/promise.ts +18 -18
- package/src/utils/ruleChecker.test.ts +336 -349
- package/src/utils/ruleChecker.ts +98 -181
- package/src/utils/sundry.ts +66 -67
package/README.md
CHANGED
|
@@ -387,6 +387,84 @@ function InfiniteList({ items, onLoadMore }) {
|
|
|
387
387
|
- `className`: CSS class name for the wrapper element.
|
|
388
388
|
- `style`: Inline styles for the wrapper element.
|
|
389
389
|
|
|
390
|
+
#### `<Repeat>` (v1.3.13+)
|
|
391
|
+
|
|
392
|
+
A declarative repeat-render component, commonly used for skeleton screens and placeholders.
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
import { Repeat } from "@wwog/react";
|
|
396
|
+
|
|
397
|
+
function SkeletonList() {
|
|
398
|
+
return (
|
|
399
|
+
<Repeat times={5}>
|
|
400
|
+
{(i) => <SkeletonItem key={i} />}
|
|
401
|
+
</Repeat>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
- `times`: Number of times to repeat. Renders nothing when `<= 0`.
|
|
407
|
+
- `children`: Render function receiving the current 0-based index.
|
|
408
|
+
|
|
409
|
+
#### `<Portal>` (v1.3.13+)
|
|
410
|
+
|
|
411
|
+
A declarative `createPortal` wrapper that renders children into a specified DOM node. Handles SSR safely by deferring mount until the client is ready.
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
import { Portal } from "@wwog/react";
|
|
415
|
+
|
|
416
|
+
// Render into document.body (default)
|
|
417
|
+
function Modal({ children }) {
|
|
418
|
+
return <Portal>{children}</Portal>;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Render into a specific element
|
|
422
|
+
function Tooltip({ children }) {
|
|
423
|
+
return (
|
|
424
|
+
<Portal to={document.getElementById("overlay-root")}>
|
|
425
|
+
{children}
|
|
426
|
+
</Portal>
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Disable portal and render inline
|
|
431
|
+
function ConditionalPortal({ usePortal, children }) {
|
|
432
|
+
return <Portal disabled={!usePortal}>{children}</Portal>;
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- `to`: Target DOM element to mount into. Defaults to `document.body`.
|
|
437
|
+
- `disabled`: When `true`, renders children inline without a portal. Defaults to `false`.
|
|
438
|
+
- `children`: Child elements to render into the portal.
|
|
439
|
+
|
|
440
|
+
#### `<Boundary>` (v1.3.13+)
|
|
441
|
+
|
|
442
|
+
A declarative Error Boundary wrapper with render-prop fallback and reset capability.
|
|
443
|
+
|
|
444
|
+
```tsx
|
|
445
|
+
import { Boundary } from "@wwog/react";
|
|
446
|
+
|
|
447
|
+
function App() {
|
|
448
|
+
return (
|
|
449
|
+
<Boundary
|
|
450
|
+
fallback={(error, reset) => (
|
|
451
|
+
<div>
|
|
452
|
+
<p>Something went wrong: {error.message}</p>
|
|
453
|
+
<button onClick={reset}>Retry</button>
|
|
454
|
+
</div>
|
|
455
|
+
)}
|
|
456
|
+
onError={(error, info) => reportError(error, info)}
|
|
457
|
+
>
|
|
458
|
+
<RiskyComponent />
|
|
459
|
+
</Boundary>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
- `fallback`: Render function called when an error is caught. Receives `(error: Error, reset: () => void)`.
|
|
465
|
+
- `onError`: Optional callback for error reporting (e.g. logging to Sentry).
|
|
466
|
+
- `children`: Child elements to protect.
|
|
467
|
+
|
|
390
468
|
#### `<SizeBox>`
|
|
391
469
|
|
|
392
470
|
Create a fixed-size container for layout adjustment and spacing control.
|
package/dist/index.d.mts
CHANGED
|
@@ -461,6 +461,103 @@ interface ObserverProps {
|
|
|
461
461
|
*/
|
|
462
462
|
declare const Observer: React$1.FC<ObserverProps>;
|
|
463
463
|
|
|
464
|
+
interface RepeatProps {
|
|
465
|
+
/**
|
|
466
|
+
* @description_en Number of times to repeat.
|
|
467
|
+
* @description_zh 重复次数。
|
|
468
|
+
*/
|
|
469
|
+
times: number;
|
|
470
|
+
/**
|
|
471
|
+
* @description_en Render function receiving the current index (0-based).
|
|
472
|
+
* @description_zh 渲染函数,接收当前索引(从 0 开始)。
|
|
473
|
+
*/
|
|
474
|
+
children: (index: number) => ReactNode;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* @description_zh 声明式重复渲染组件,常用于骨架屏、占位符等场景。
|
|
478
|
+
* @description_en Declarative repeat-render component, commonly used for skeleton screens and placeholders.
|
|
479
|
+
* @component
|
|
480
|
+
* @example
|
|
481
|
+
* ```tsx
|
|
482
|
+
* <Repeat times={3}>
|
|
483
|
+
* {(i) => <Skeleton key={i} />}
|
|
484
|
+
* </Repeat>
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
declare function Repeat({ times, children }: RepeatProps): ReactNode;
|
|
488
|
+
|
|
489
|
+
interface PortalProps {
|
|
490
|
+
/**
|
|
491
|
+
* @description_en Target DOM element to mount into. Defaults to document.body.
|
|
492
|
+
* @description_zh 挂载目标 DOM 元素,默认为 document.body。
|
|
493
|
+
* @default document.body
|
|
494
|
+
*/
|
|
495
|
+
to?: Element | null;
|
|
496
|
+
/**
|
|
497
|
+
* @description_en Child elements to render into the portal.
|
|
498
|
+
* @description_zh 要渲染到 portal 中的子元素。
|
|
499
|
+
*/
|
|
500
|
+
children?: ReactNode;
|
|
501
|
+
/**
|
|
502
|
+
* @description_en Whether to disable the portal and render children inline. Default is false.
|
|
503
|
+
* @description_zh 是否禁用 portal,直接内联渲染子元素。默认为 false。
|
|
504
|
+
* @default false
|
|
505
|
+
*/
|
|
506
|
+
disabled?: boolean;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* @description_zh 声明式 Portal 组件,将子元素渲染到指定 DOM 节点,常用于模态框、浮层等场景。
|
|
510
|
+
* @description_en Declarative Portal component that renders children into a specified DOM node, commonly used for modals and overlays.
|
|
511
|
+
* @component
|
|
512
|
+
* @example
|
|
513
|
+
* ```tsx
|
|
514
|
+
* <Portal>
|
|
515
|
+
* <Modal />
|
|
516
|
+
* </Portal>
|
|
517
|
+
*
|
|
518
|
+
* <Portal to={document.getElementById('overlay-root')}>
|
|
519
|
+
* <Tooltip />
|
|
520
|
+
* </Portal>
|
|
521
|
+
* ```
|
|
522
|
+
*/
|
|
523
|
+
declare function Portal({ to, children, disabled }: PortalProps): ReactNode;
|
|
524
|
+
|
|
525
|
+
interface BoundaryProps {
|
|
526
|
+
/**
|
|
527
|
+
* @description_en Fallback UI to render when an error is caught. Receives the error and a reset function.
|
|
528
|
+
* @description_zh 捕获到错误时渲染的降级 UI,接收错误对象和重置函数。
|
|
529
|
+
*/
|
|
530
|
+
fallback: (error: Error, reset: () => void) => ReactNode;
|
|
531
|
+
/**
|
|
532
|
+
* @description_en Called when an error is caught, useful for logging.
|
|
533
|
+
* @description_zh 捕获到错误时的回调,可用于上报日志。
|
|
534
|
+
* @optional
|
|
535
|
+
*/
|
|
536
|
+
onError?: (error: Error, info: React$1.ErrorInfo) => void;
|
|
537
|
+
/**
|
|
538
|
+
* @description_en Child elements to protect.
|
|
539
|
+
* @description_zh 需要保护的子元素。
|
|
540
|
+
*/
|
|
541
|
+
children?: ReactNode;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* @description_zh Error Boundary 的声明式封装,通过 render prop 提供降级 UI 和重置能力。
|
|
545
|
+
* @description_en Declarative Error Boundary wrapper with render-prop fallback and reset capability.
|
|
546
|
+
* @component
|
|
547
|
+
* @example
|
|
548
|
+
* ```tsx
|
|
549
|
+
* <Boundary fallback={(error, reset) => (
|
|
550
|
+
* <div>
|
|
551
|
+
* <p>出错了: {error.message}</p>
|
|
552
|
+
* <button onClick={reset}>重试</button>
|
|
553
|
+
* </div>
|
|
554
|
+
* )}>
|
|
555
|
+
* <RiskyComponent />
|
|
556
|
+
* </Boundary>
|
|
557
|
+
* ```
|
|
558
|
+
*/
|
|
559
|
+
declare function Boundary(props: BoundaryProps): ReactNode;
|
|
560
|
+
|
|
464
561
|
interface ArrayRenderProps<T> {
|
|
465
562
|
items: T[];
|
|
466
563
|
renderItem: (item: T, index: number) => React$1.ReactNode;
|
|
@@ -540,12 +637,6 @@ interface UseControlledOptions<T> {
|
|
|
540
637
|
}
|
|
541
638
|
declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
|
|
542
639
|
|
|
543
|
-
/**
|
|
544
|
-
* @en Callback function type for state change listeners
|
|
545
|
-
* @zh 状态变更监听器的回调函数类型
|
|
546
|
-
* @template T The type of the state / 状态的类型
|
|
547
|
-
*/
|
|
548
|
-
type CreateStateListener<T> = (state: T) => void;
|
|
549
640
|
/**
|
|
550
641
|
* @zh 如果需要在变更状态时执行副作用,可以传入函数,对于异步函数,会在更改状态后执行,不会阻塞状态更新, 尽可能在外部使用useEffect处理异步副作用
|
|
551
642
|
* @en If you need to perform side effects when changing the state, you can pass a function. For asynchronous functions, it will be executed after the state changes without blocking the state update, so it's best to use useEffect for handling asynchronous side effects.
|
|
@@ -622,7 +713,7 @@ interface ExternalState<T, U = T> {
|
|
|
622
713
|
useGetter: () => U;
|
|
623
714
|
}
|
|
624
715
|
interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
|
|
625
|
-
__listeners:
|
|
716
|
+
__listeners: (() => void)[];
|
|
626
717
|
}
|
|
627
718
|
/**
|
|
628
719
|
*
|
|
@@ -659,7 +750,7 @@ declare function createExternalState<T, U = T>(initialState: T | (() => T), opti
|
|
|
659
750
|
interface StorageStateOptions<T, U> {
|
|
660
751
|
sideEffect?: (newState: T) => void;
|
|
661
752
|
transform?: Transform<T, U>;
|
|
662
|
-
storageType:
|
|
753
|
+
storageType: 'local' | 'session';
|
|
663
754
|
}
|
|
664
755
|
declare function createStorageState<T, U = T>(key: string, initialState: T, options?: StorageStateOptions<T, U>): ExternalState<T, U>;
|
|
665
756
|
|
|
@@ -774,5 +865,5 @@ declare function ruleChecker<T extends Record<string, unknown>, R extends RuleDe
|
|
|
774
865
|
declare function getCurrentBreakpoint(breakpointDesc: BreakpointDesc, width: number): BreakpointName;
|
|
775
866
|
declare function useScreen(breakpointDesc?: BreakpointDesc): "base" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
|
|
776
867
|
|
|
777
|
-
export { ArrayRender, Counter, DateRender, DefBreakpointDesc, False, If, Observer, Pipe, Scope, SizeBox, Styles, Switch, Toggle, True, When, breakpoints, childrenLoop, createExternalState, createStorageState, cx, formatDate, getCurrentBreakpoint, ruleChecker, safePromiseTry, safePromiseWithResolvers, useControlled, useScreen };
|
|
778
|
-
export type { ApplyRules, ArrayRenderProps, ArrayRule, ArraySpecificProps, BaseRule, BooleanRule, BreakpointDesc, BreakpointName,
|
|
868
|
+
export { ArrayRender, Boundary, Counter, DateRender, DefBreakpointDesc, False, If, Observer, Pipe, Portal, Repeat, Scope, SizeBox, Styles, Switch, Toggle, True, When, breakpoints, childrenLoop, createExternalState, createStorageState, cx, formatDate, getCurrentBreakpoint, ruleChecker, safePromiseTry, safePromiseWithResolvers, useControlled, useScreen };
|
|
869
|
+
export type { ApplyRules, ArrayRenderProps, ArrayRule, ArraySpecificProps, BaseRule, BooleanRule, BoundaryProps, BreakpointDesc, BreakpointName, CxInput, DateRenderProps, ElseIfProps, ElseProps, ExternalSideEffect, ExternalState, ExternalStateOptions, ExternalWithKernel, FalseProps, FieldRule, IfProps, LengthRuleProps, NumberRangeProps, NumberRule, ObserverProps, PipeProps, PortalProps, RepeatProps, Responsive, RuleDescription, ScopeProps, StorageStateOptions, StringRule, StringSpecificProps, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, Transform, TrueProps, UseControlledOptions, WhenProps };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import a,{useMemo as S,Fragment as v,Children as Z,isValidElement as q,cloneElement as G,useEffect as b,useState as I,useRef as O,useCallback as U}from"react";function j(e,n){if(e===void 0)return;let t=0;if(Array.isArray(e)){for(const o of e)if(n(o,t++)===!1)break}else n(e,t)}const K=(e,n)=>e===n,N=e=>a.createElement(a.Fragment,null,e.children);N.displayName="Switch_Case";const D=e=>a.createElement(a.Fragment,null,e.children);D.displayName="Switch_Default";const w=e=>{const{value:n,compare:t=K,children:o,strict:r=!1}=e,l=new Set;let s=null,u=null,f=!1;return j(o,(i,c)=>{if(!a.isValidElement(i))throw new Error(`Switch Children only accepts valid React elements at index ${c}`);const d=i.type;if(d.displayName===N.displayName){const m=i.props;if(l.has(m.value))throw new Error(`Switch found duplicate Case value at index ${c}: ${JSON.stringify(m.value)}${r?" (detected in strict mode)":""}`);if(l.add(m.value),!s&&t(n,m.value)&&(s=m.children,r===!1))return!1}else if(d.displayName===D.displayName){if(f)throw new Error(`Switch can only have one Default child at index ${c}`);if(f=!0,u=i.props.children,!r&&s)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(d.displayName||d.name||d)} at index ${c}`)}),a.createElement(a.Fragment,null,s??u)};w.displayName="Switch",w.Case=N,w.Default=D,w.createTyped=function(){return{Switch:w,Case:N,Default:D}};const A=e=>a.createElement(a.Fragment,null,e.children),x=({children:e})=>a.createElement(a.Fragment,null,e),M=e=>a.createElement(a.Fragment,null,e.children);A.displayName="If_Then",x.displayName="If_Else",M.displayName="If_ElseIf";const E=({condition:e,children:n})=>{let t=null,o=null;const r=[];if(a.Children.forEach(n,l=>{if(!a.isValidElement(l))throw new Error("If component only accepts valid React elements");const s=l.type;if(s.displayName===A.displayName){if(t)throw new Error("If component can only have one Then child");t=l}else if(s.displayName===M.displayName)r.push(l);else if(s.displayName===x.displayName){if(o)throw new Error("If component can only have one Else child");o=l}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(s.displayName||s.name||s)}`)}),e)return t?a.createElement(a.Fragment,null,t.props.children):null;for(const l of r)if(l.props.condition)return a.createElement(a.Fragment,null,l.props.children);return o?a.createElement(a.Fragment,null,o.props.children):null};E.displayName="If",E.Then=A,E.ElseIf=M,E.Else=x,E.createTyped=function(){return{If:E,Then:A,ElseIf:M,Else:x}};const Q=({condition:e,children:n})=>e?a.createElement(a.Fragment,null,n):null,X=({condition:e,children:n})=>e===!1?a.createElement(a.Fragment,null,n):null,ee=({all:e,any:n,none:t,children:o,fallback:r})=>S(()=>(e&&(n||t)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(e&&e.length>0&&e.every(Boolean)||n&&n.length>0&&n.some(Boolean)||t&&t.length>0&&t.every(l=>!l))),[e,n,t])?a.createElement(a.Fragment,null,o):a.createElement(a.Fragment,null,r||null),te=({data:e,transform:n,render:t,fallback:o})=>{const r=S(()=>n.reduce((l,s)=>s(l),e),[e,n]);return r==null?a.createElement(a.Fragment,null,o||null):a.createElement(a.Fragment,null,t(r))},ne=e=>{const{children:n,h:t,w:o,size:r,height:l,width:s,className:u}=e;return a.createElement("div",{style:{width:r||o||s,height:r||t||l,flexShrink:0},className:u},n)},re=({let:e,props:n,children:t,fallback:o})=>{const r=S(()=>typeof e=="function"?e(n):e,[e,n]);return!t||!Object.keys(r).length?a.createElement(a.Fragment,null,o||null):a.createElement(a.Fragment,null,t(r))};function C(...e){const n=new Set;for(const t of e)if(t){if(typeof t=="string")n.add(t);else if(Array.isArray(t))t.forEach(o=>n.add(o));else if(typeof t=="object")for(const[o,r]of Object.entries(t))r&&n.add(o)}return Array.from(n).join(" ")}const oe=e=>typeof e=="object"&&!!e,P=({className:e,children:n,asWrapper:t=!1})=>{if(!n)return null;if(!e)return a.createElement(v,null,n);const o=typeof e=="string"?e:C(...Object.values(e));if(t)return a.createElement(t===!0?"div":t,{className:o},n);if(Z.count(n)>1)return console.error("<Styles>: children has more than one child. Please check your code."),a.createElement(v,null,n);if(q(n)){const r=n;let l=r?.props?.className;return r?.type?.displayName===P.displayName&&oe(l)&&(l=C(...Object.values(l))),G(n,{className:C(o,l)})}return console.error("<Styles>: children is not a valid React element. Please check your code."),a.createElement(v,null,n)};P.displayName="W/Styles";const le=e=>{const{index:n=0,options:t,next:o,render:r}=e;b(()=>{if(t.length<n+1)throw new Error(`Index ${n} is out of bounds for options array of length ${t.length}. Defaulting to first option.`)},[n,t]);const[l,s]=I(n),u=()=>{s(f=>t.length?o?o(f,t):(f+1)%t.length:f)};return r(t[l],u)},se=({onIntersect:e,threshold:n=.1,root:t=null,rootMargin:o="0px",triggerOnce:r=!1,disabled:l=!1,children:s,className:u,style:f})=>{const i=O(null),c=O(null),d=O(!1);return b(()=>{if(l||!i.current)return;if(!window.IntersectionObserver){console.warn("IntersectionObserver is not supported in this browser");return}const m=i.current,g=y=>{y.forEach($=>{r&&d.current||(e($,c.current),r&&(d.current=!0,c.current?.unobserve(m)))})};return c.current=new IntersectionObserver(g,{root:t,rootMargin:o,threshold:n}),c.current.observe(m),()=>{c.current&&c.current.disconnect()}},[e,n,t,o,r,l]),b(()=>{r||(d.current=!1)},[r]),a.createElement("div",{ref:i,className:u,style:f},s)};function ae(e){const{items:n,renderItem:t,filter:o,renderEmpty:r,sort:l}=e;if(!n)return console.error("ArrayRender: items is null"),null;if(n.length===0)return r?r():null;if(l){let s=[...n];return o&&(s=s.filter(o)),s=s.sort(l),s.length===0?r?r():null:a.createElement(v,null,s.map((u,f)=>t(u,f)))}return a.createElement(v,null,n.map((s,u)=>o&&!o(s)?null:t(s,u)))}function ie({source:e,format:n,children:t}){const o=S(()=>{if(e instanceof Date)return e;if(typeof e=="string"||typeof e=="number"){const l=new Date(e);return isNaN(l.getTime())?null:l}return null},[e]),r=S(()=>o?n?n(o):o.toLocaleString():null,[o,n]);return!r||!t?null:a.createElement(a.Fragment,null,t(r))}const ue="onChange",ce="value";function fe(e){const{defaultValue:n,onBeforeChange:t,trigger:o=ue,valuePropName:r=ce,props:l}=e,s=Object.prototype.hasOwnProperty.call(l,r),[u,f]=I(n),i=s?l[r]:u,c=S(()=>l[o],[l,o]),d=U(m=>{const g=typeof m=="function"?m(i):m;t&&t(g,i)===!1||(s||f(g),c&&c(g))},[s,t,i,c]);return[i,d]}function de(e,...n){try{const t=e(...n);return t instanceof Promise?t:Promise.resolve(t)}catch(t){return Promise.reject(t)}}const J=typeof Promise.try=="function"?Promise.try.bind(Promise):de,me=typeof Promise.withResolvers=="function"?Promise.withResolvers.bind(Promise):()=>{let e,n;return{promise:new Promise((t,o)=>{e=t,n=o}),resolve:e,reject:n}};function W(e,n={}){let t=typeof e=="function"?e():e;const o=[],{sideEffect:r,transform:l}=n,s=()=>{const i=t;return l?.get?l.get(i):i},u=i=>{const c=t,d=l?.get?l.get(c):c;t=l?.set?l.set(typeof i=="function"?i(d):i):typeof i=="function"?i(d):i,o.forEach(m=>m(t)),r&&J(r,t,c).catch(m=>{console.error("Error in external state side effect, Please do it within side effects:",m)})},f=()=>{const[i,c]=a.useState(t);return a.useEffect(()=>(o.push(c),()=>{const d=o.indexOf(c);d>-1&&o.splice(d,1)}),[]),[l?.get?l.get(i):i,u]};return{get:s,set:u,use:f,useGetter:()=>{const[i]=f();return i},__listeners:o}}function pe(e,n,t){const{storageType:o="local",sideEffect:r,transform:l}=t??{};let s=n;if(typeof window<"u"){const u=(o==="local"?localStorage:sessionStorage).getItem(e);if(u)try{s=JSON.parse(u)}catch(f){console.warn(`Failed to parse ${o}Storage value for key "${e}", using initial state:`,f),s=n}}return W(s,{sideEffect:u=>{typeof window<"u"&&(o==="local"?localStorage:sessionStorage).setItem(e,JSON.stringify(u)),r?.(u)},transform:l})}function he(e,n){const t=n||new Date,o=t.getFullYear(),r=t.getMonth()+1,l=t.getDate(),s=t.getHours(),u=t.getMinutes(),f=t.getSeconds(),i=t.getMilliseconds(),c=t.getDay(),d=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],m=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],g=["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"],$=m[c],Y=d[c],R=r-1,_=y[R],H=g[R],z={YY:o.toString().slice(2),YYYY:o.toString(),M:r.toString(),MM:r.toString().padStart(2,"0"),MMM:H,MMMM:_,D:l.toString(),DD:l.toString().padStart(2,"0"),d:c.toString(),dd:Y,ddd:Y,dddd:$,H:s.toString(),HH:s.toString().padStart(2,"0"),h:(s%12).toString(),hh:(s%12).toString().padStart(2,"0"),m:u.toString(),mm:u.toString().padStart(2,"0"),s:f.toString(),ss:f.toString().padStart(2,"0"),SSS:i.toString().padStart(3,"0"),Z:"+08:00",ZZ:"+0800",A:s<12?"AM":"PM",a:s<12?"am":"pm"};return e.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,V=>z[V])}class ge{count=0;next(){return this.count++}}const F=["base","xs","sm","md","lg","xl","2xl","3xl"],L={xs:475,sm:640,md:768,lg:1024,xl:1280,"2xl":1536,"3xl":1920};function p(e,n,t){t&&(e[n]||(e[n]=[]),e[n].push(t))}function ye(e){return e==null?!1:typeof e=="string"?e.trim().length>0:Array.isArray(e)?e.length>0:!0}function Ee(e){return e!=null}function h(e,n){return`${String(e)} ${n}`}const T={email:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,url:/^(https?:\/\/)?([\w.-]+)\.([a-z]{2,6})([/\w .-]*)*\/?$/,phone:/^1[3-9]\d{9}$/};function k(e,n,t,o,r){const l=ye(n),s=Ee(n);if(t.required&&!l){p(r,e,t.message??h(e,"\u4E3A\u5FC5\u586B\u9879"));return}if(!(!s&&!t.required)){if(t.dependsOn){const u=t.dependsOn(o);u===!1?p(r,e,t.message??h(e,"\u4F9D\u8D56\u6761\u4EF6\u672A\u6EE1\u8DB3")):typeof u=="string"&&p(r,e,u)}if(typeof n=="string"){const u=t,{len:f,min:i,max:c,regex:d,email:m,url:g,phone:y}=u;typeof f=="number"&&n.length!==f&&p(r,e,t.message??h(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${f}`)),typeof i=="number"&&n.length<i&&p(r,e,t.message??h(e,`\u957F\u5EA6\u4E0D\u80FD\u5C11\u4E8E ${i}`)),typeof c=="number"&&n.length>c&&p(r,e,t.message??h(e,`\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${c}`)),d&&!d.test(n)&&p(r,e,t.message??h(e,"\u683C\u5F0F\u4E0D\u6B63\u786E")),m&&!T.email.test(n)&&p(r,e,t.message??h(e,"\u4E0D\u662F\u6709\u6548\u7684\u90AE\u7BB1")),g&&!T.url.test(n)&&p(r,e,t.message??h(e,"\u4E0D\u662F\u6709\u6548\u7684URL")),y&&!T.phone.test(n)&&p(r,e,t.message??h(e,"\u4E0D\u662F\u6709\u6548\u7684\u624B\u673A\u53F7"))}if(typeof n=="number"){const u=t,{min:f,max:i}=u;typeof f=="number"&&n<f&&p(r,e,t.message??h(e,`\u4E0D\u80FD\u5C0F\u4E8E ${f}`)),typeof i=="number"&&n>i&&p(r,e,t.message??h(e,`\u4E0D\u80FD\u5927\u4E8E ${i}`))}if(Array.isArray(n)){const u=t,{len:f,min:i,max:c,unique:d,elementRule:m}=u;typeof f=="number"&&n.length!==f&&p(r,e,t.message??h(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${f}`)),typeof i=="number"&&n.length<i&&p(r,e,t.message??h(e,`\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E ${i}`)),typeof c=="number"&&n.length>c&&p(r,e,t.message??h(e,`\u957F\u5EA6\u4E0D\u80FD\u5927\u4E8E ${c}`)),d&&new Set(n).size!==n.length&&p(r,e,t.message??h(e,"\u5143\u7D20\u5FC5\u987B\u552F\u4E00")),m&&n.forEach((g,y)=>{k(`${String(e)}[${y}]`,g,m,o,r)})}if(t.validator){const u=t.validator?.(n,o);u===!1?p(r,e,t.message??h(e,"\u6821\u9A8C\u672A\u901A\u8FC7")):typeof u=="string"&&p(r,e,u)}}}function Se(e,n){const t={};for(const r in n){const l=r,s=n[l];if(!s)continue;const u=e[l];if(Array.isArray(s))for(const f of s)k(l,u,f,e,t);else k(l,u,s,e,t)}const o=Object.values(t).reduce((r,l)=>(l&&r.push(...l),r),[]);return o.length>0?{valid:!1,errors:o,fieldErrors:t}:{valid:!0,data:e}}const we=[...F].reverse();function B(e,n){for(const t of we){const o=e[t];if(o!==void 0&&!Number.isNaN(o)&&n>=o)return t}return"base"}function ve(e=L){const[n,t]=I(B(e,window.innerWidth));return b(()=>{let o=[],r=[];const l=()=>{r.forEach(c=>c()),o=[],r=[];const s=B(e,window.innerWidth);t(s);const u=F.indexOf(s),f=F[u+1];if(f&&e[f]!==void 0){const c=e[f];if(Number.isNaN(c))throw new Error(`Invalid breakpoint value for ${f}: ${e[f]}`);{const d=window.matchMedia(`(min-width: ${c}px)`);o.push(d);const m=()=>l();d.addEventListener("change",m),r.push(()=>d.removeEventListener("change",m))}}const i=F[u-1];if(i&&e[i]!==void 0){const c=e[i];if(Number.isNaN(c))throw new Error(`Invalid breakpoint value for ${i}: ${e[i]}`);{const d=window.matchMedia(`(max-width: ${c-1}px)`);o.push(d);const m=()=>l();d.addEventListener("change",m),r.push(()=>d.removeEventListener("change",m))}}};return l(),()=>{r.forEach(s=>s())}},[e]),n}export{ae as ArrayRender,ge as Counter,ie as DateRender,L as DefBreakpointDesc,X as False,E as If,se as Observer,te as Pipe,re as Scope,ne as SizeBox,P as Styles,w as Switch,le as Toggle,Q as True,ee as When,F as breakpoints,j as childrenLoop,W as createExternalState,pe as createStorageState,C as cx,he as formatDate,B as getCurrentBreakpoint,Se as ruleChecker,J as safePromiseTry,me as safePromiseWithResolvers,fe as useControlled,ve as useScreen};
|
|
1
|
+
import i,{useMemo as S,Fragment as v,Children as q,isValidElement as U,cloneElement as G,useEffect as b,useState as A,useRef as N,Component as K,useCallback as Q,useSyncExternalStore as X}from"react";import{createPortal as ee}from"react-dom";function j(e,r){if(e===void 0)return;let t=0;if(Array.isArray(e)){for(const o of e)if(r(o,t++)===!1)break}else r(e,t)}const te=(e,r)=>e===r,x=e=>i.createElement(i.Fragment,null,e.children);x.displayName="Switch_Case";const C=e=>i.createElement(i.Fragment,null,e.children);C.displayName="Switch_Default";const F=e=>{const{value:r,compare:t=te,children:o,strict:n=!1}=e,l=new Set;let s=null,c=null,a=!1;return j(o,(u,f)=>{if(!i.isValidElement(u))throw new Error(`Switch Children only accepts valid React elements at index ${f}`);const d=u.type;if(d.displayName===x.displayName){const m=u.props;if(l.has(m.value))throw new Error(`Switch found duplicate Case value at index ${f}: ${JSON.stringify(m.value)}${n?" (detected in strict mode)":""}`);if(l.add(m.value),!s&&t(r,m.value)&&(s=m.children,n===!1))return!1}else if(d.displayName===C.displayName){if(a)throw new Error(`Switch can only have one Default child at index ${f}`);if(a=!0,c=u.props.children,!n&&s)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(d.displayName||d.name||d)} at index ${f}`)}),i.createElement(i.Fragment,null,s??c)};F.displayName="Switch",F.Case=x,F.Default=C,F.createTyped=function(){return{Switch:F,Case:x,Default:C}};const M=e=>i.createElement(i.Fragment,null,e.children),I=({children:e})=>i.createElement(i.Fragment,null,e),$=e=>i.createElement(i.Fragment,null,e.children);M.displayName="If_Then",I.displayName="If_Else",$.displayName="If_ElseIf";const w=({condition:e,children:r})=>{let t=null,o=null;const n=[];if(i.Children.forEach(r,l=>{if(!i.isValidElement(l))throw new Error("If component only accepts valid React elements");const s=l.type;if(s.displayName===M.displayName){if(t)throw new Error("If component can only have one Then child");t=l}else if(s.displayName===$.displayName)n.push(l);else if(s.displayName===I.displayName){if(o)throw new Error("If component can only have one Else child");o=l}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(s.displayName||s.name||s)}`)}),e)return t?i.createElement(i.Fragment,null,t.props.children):null;for(const l of n)if(l.props.condition)return i.createElement(i.Fragment,null,l.props.children);return o?i.createElement(i.Fragment,null,o.props.children):null};w.displayName="If",w.Then=M,w.ElseIf=$,w.Else=I,w.createTyped=function(){return{If:w,Then:M,ElseIf:$,Else:I}};const re=({condition:e,children:r})=>e?i.createElement(i.Fragment,null,r):null,ne=({condition:e,children:r})=>e===!1?i.createElement(i.Fragment,null,r):null,oe=({all:e,any:r,none:t,children:o,fallback:n})=>S(()=>(e&&(r||t)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(e&&e.length>0&&e.every(Boolean)||r&&r.length>0&&r.some(Boolean)||t&&t.length>0&&t.every(l=>!l))),[e,r,t])?i.createElement(i.Fragment,null,o):i.createElement(i.Fragment,null,n||null),le=({data:e,transform:r,render:t,fallback:o})=>{const n=S(()=>r.reduce((l,s)=>s(l),e),[e,r]);return n==null?i.createElement(i.Fragment,null,o||null):i.createElement(i.Fragment,null,t(n))},se=e=>{const{children:r,h:t,w:o,size:n,height:l,width:s,className:c}=e;return i.createElement("div",{style:{width:n||o||s,height:n||t||l,flexShrink:0},className:c},r)},ae=({let:e,props:r,children:t,fallback:o})=>{const n=S(()=>typeof e=="function"?e(r):e,[e,r]);return!t||!Object.keys(n).length?i.createElement(i.Fragment,null,o||null):i.createElement(i.Fragment,null,t(n))};function O(...e){const r=new Set;for(const t of e)if(t){if(typeof t=="string")r.add(t);else if(Array.isArray(t))t.forEach(o=>r.add(o));else if(typeof t=="object")for(const[o,n]of Object.entries(t))n&&r.add(o)}return Array.from(r).join(" ")}const ie=e=>typeof e=="object"&&!!e,P=({className:e,children:r,asWrapper:t=!1})=>{if(!r)return null;if(!e)return i.createElement(v,null,r);const o=typeof e=="string"?e:O(...Object.values(e));if(t)return i.createElement(t===!0?"div":t,{className:o},r);if(q.count(r)>1)return console.error("<Styles>: children has more than one child. Please check your code."),i.createElement(v,null,r);if(U(r)){const n=r;let l=n?.props?.className;return n?.type?.displayName===P.displayName&&ie(l)&&(l=O(...Object.values(l))),G(r,{className:O(o,l)})}return console.error("<Styles>: children is not a valid React element. Please check your code."),i.createElement(v,null,r)};P.displayName="W/Styles";const ue=e=>{const{index:r=0,options:t,next:o,render:n}=e;b(()=>{if(t.length<r+1)throw new Error(`Index ${r} is out of bounds for options array of length ${t.length}. Defaulting to first option.`)},[r,t]);const[l,s]=A(r),c=()=>{s(a=>t.length?o?o(a,t):(a+1)%t.length:a)};return n(t[l],c)},ce=({onIntersect:e,threshold:r=.1,root:t=null,rootMargin:o="0px",triggerOnce:n=!1,disabled:l=!1,children:s,className:c,style:a})=>{const u=N(null),f=N(null),d=N(!1);return b(()=>{if(l||!u.current)return;if(!window.IntersectionObserver){console.warn("IntersectionObserver is not supported in this browser");return}const m=u.current,h=y=>{y.forEach(E=>{n&&d.current||(e(E,f.current),n&&(d.current=!0,f.current?.unobserve(m)))})};return f.current=new IntersectionObserver(h,{root:t,rootMargin:o,threshold:r}),f.current.observe(m),()=>{f.current&&f.current.disconnect()}},[e,r,t,o,n,l]),b(()=>{n||(d.current=!1)},[n]),i.createElement("div",{ref:u,className:c,style:a},s)};function fe({times:e,children:r}){if(e<=0)return null;const t=[];for(let o=0;o<e;o++)t.push(r(o));return i.createElement(v,null,t)}function de({to:e,children:r,disabled:t=!1}){const[o,n]=A(!1),l=N(e);if(l.current=e,b(()=>{n(!0)},[]),t)return i.createElement(i.Fragment,null,r);if(!o)return null;const s=l.current??document.body;return ee(r,s)}class me extends K{state={error:null};static getDerivedStateFromError(r){return{error:r}}componentDidCatch(r,t){this.props.onError?.(r,t)}reset=()=>{this.setState({error:null})};render(){return this.state.error?this.props.fallback(this.state.error,this.reset):this.props.children}}function he(e){return i.createElement(me,{...e})}function pe(e){const{items:r,renderItem:t,filter:o,renderEmpty:n,sort:l}=e;if(!r)return console.error("ArrayRender: items is null"),null;if(r.length===0)return n?n():null;if(l){let a=[...r];return o&&(a=a.filter(o)),a=a.sort(l),a.length===0?n?n():null:i.createElement(v,null,a.map((u,f)=>t(u,f)))}let s=0;const c=r.map((a,u)=>o&&!o(a)?(s++,null):t(a,u));return i.createElement(v,null,s===r.length?n?n():null:c)}function ge({source:e,format:r,children:t}){const o=S(()=>{if(e instanceof Date)return e;if(typeof e=="string"||typeof e=="number"){const l=new Date(e);return isNaN(l.getTime())?null:l}return null},[e]),n=S(()=>o?r?r(o):o.toLocaleString():null,[o,r]);return!n||!t?null:i.createElement(i.Fragment,null,t(n))}const ye="onChange",Ee="value";function Se(e){const{defaultValue:r,onBeforeChange:t,trigger:o=ye,valuePropName:n=Ee,props:l}=e,s=Object.prototype.hasOwnProperty.call(l,n),[c,a]=A(r),u=s?l[n]:c,f=S(()=>l[o],[l,o]),d=Q(m=>{const h=typeof m=="function"?m(u):m;t&&t(h,u)===!1||(s||a(h),f&&f(h))},[s,t,u,f]);return[u,d]}function we(e,...r){try{const t=e(...r);return t instanceof Promise?t:Promise.resolve(t)}catch(t){return Promise.reject(t)}}const J=typeof Promise.try=="function"?Promise.try.bind(Promise):we,ve=typeof Promise.withResolvers=="function"?Promise.withResolvers.bind(Promise):()=>{let e,r;return{promise:new Promise((t,o)=>{e=t,r=o}),resolve:e,reject:r}};function W(e,r={}){let t=typeof e=="function"?e():e;const o=[],{sideEffect:n,transform:l}=r,s=()=>{const u=t;return l?.get?l.get(u):u},c=u=>{const f=t,d=l?.get?l.get(f):f;t=l?.set?l.set(typeof u=="function"?u(d):u):typeof u=="function"?u(d):u,o.forEach(m=>m()),n&&J(n,t,f).catch(m=>{console.error("Error in external state side effect, Please do it within side effects:",m)})},a=()=>{const u=X(f=>(o.push(f),()=>{const d=o.indexOf(f);d>-1&&o.splice(d,1)}),()=>t,()=>t);return[l?.get?l.get(u):u,c]};return{get:s,set:c,use:a,useGetter:()=>{const[u]=a();return u},__listeners:o}}function Fe(e,r,t){const{storageType:o="local",sideEffect:n,transform:l}=t??{};let s=r;if(typeof window<"u"){const c=(o==="local"?localStorage:sessionStorage).getItem(e);if(c)try{s=JSON.parse(c)}catch(a){console.warn(`Failed to parse ${o}Storage value for key "${e}", using initial state:`,a),s=r}}return W(s,{sideEffect:c=>{typeof window<"u"&&(o==="local"?localStorage:sessionStorage).setItem(e,JSON.stringify(c)),n?.(c)},transform:l})}function be(e,r){const t=r||new Date,o=t.getFullYear(),n=t.getMonth()+1,l=t.getDate(),s=t.getHours(),c=t.getMinutes(),a=t.getSeconds(),u=t.getMilliseconds(),f=t.getDay(),d=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],m=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],h=["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"],E=m[f],R=d[f],Y=n-1,H=y[Y],z=h[Y],V={YY:o.toString().slice(2),YYYY:o.toString(),M:n.toString(),MM:n.toString().padStart(2,"0"),MMM:z,MMMM:H,D:l.toString(),DD:l.toString().padStart(2,"0"),d:f.toString(),dd:R,ddd:R,dddd:E,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:a.toString(),ss:a.toString().padStart(2,"0"),SSS:u.toString().padStart(3,"0"),Z:"+08:00",ZZ:"+0800",A:s<12?"AM":"PM",a:s<12?"am":"pm"};return e.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,Z=>V[Z])}class Ne{count=0;next(){return this.count++}}const D=["base","xs","sm","md","lg","xl","2xl","3xl"],L={xs:475,sm:640,md:768,lg:1024,xl:1280,"2xl":1536,"3xl":1920};function p(e,r,t){t&&(e[r]||(e[r]=[]),e[r].push(t))}function De(e){return e==null?!1:typeof e=="string"?e.trim().length>0:Array.isArray(e)?e.length>0:!0}function Ae(e){return e!=null}function g(e,r){return`${String(e)} ${r}`}const k={email:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,url:/^(https?:\/\/)?([\w.-]+)\.([a-z]{2,6})([/\w .-]*)*\/?$/,phone:/^1[3-9]\d{9}$/};function T(e,r,t,o,n){const l=De(r),s=Ae(r);if(t.required&&!l){p(n,e,t.message??g(e,"\u4E3A\u5FC5\u586B\u9879"));return}if(!(!s&&!t.required)){if(t.dependsOn){const c=t.dependsOn(o);c===!1?p(n,e,t.message??g(e,"\u4F9D\u8D56\u6761\u4EF6\u672A\u6EE1\u8DB3")):typeof c=="string"&&p(n,e,c)}if(typeof r=="string"){const c=t,{len:a,min:u,max:f,regex:d,email:m,url:h,phone:y}=c;typeof a=="number"&&r.length!==a&&p(n,e,t.message??g(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${a}`)),typeof u=="number"&&r.length<u&&p(n,e,t.message??g(e,`\u957F\u5EA6\u4E0D\u80FD\u5C11\u4E8E ${u}`)),typeof f=="number"&&r.length>f&&p(n,e,t.message??g(e,`\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${f}`)),d&&!d.test(r)&&p(n,e,t.message??g(e,"\u683C\u5F0F\u4E0D\u6B63\u786E")),m&&!k.email.test(r)&&p(n,e,t.message??g(e,"\u4E0D\u662F\u6709\u6548\u7684\u90AE\u7BB1")),h&&!k.url.test(r)&&p(n,e,t.message??g(e,"\u4E0D\u662F\u6709\u6548\u7684URL")),y&&!k.phone.test(r)&&p(n,e,t.message??g(e,"\u4E0D\u662F\u6709\u6548\u7684\u624B\u673A\u53F7"))}if(typeof r=="number"){const c=t,{min:a,max:u}=c;typeof a=="number"&&r<a&&p(n,e,t.message??g(e,`\u4E0D\u80FD\u5C0F\u4E8E ${a}`)),typeof u=="number"&&r>u&&p(n,e,t.message??g(e,`\u4E0D\u80FD\u5927\u4E8E ${u}`))}if(Array.isArray(r)){const c=t,{len:a,min:u,max:f,unique:d,elementRule:m}=c;typeof a=="number"&&r.length!==a&&p(n,e,t.message??g(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${a}`)),typeof u=="number"&&r.length<u&&p(n,e,t.message??g(e,`\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E ${u}`)),typeof f=="number"&&r.length>f&&p(n,e,t.message??g(e,`\u957F\u5EA6\u4E0D\u80FD\u5927\u4E8E ${f}`)),d&&new Set(r).size!==r.length&&p(n,e,t.message??g(e,"\u5143\u7D20\u5FC5\u987B\u552F\u4E00")),m&&r.forEach((h,y)=>{T(`${String(e)}[${y}]`,h,m,o,n)})}if(t.validator){const c=t.validator?.(r,o);c===!1?p(n,e,t.message??g(e,"\u6821\u9A8C\u672A\u901A\u8FC7")):typeof c=="string"&&p(n,e,c)}}}function xe(e,r){const t={};for(const n in r){const l=n,s=r[l];if(!s)continue;const c=e[l];if(Array.isArray(s))for(const a of s)T(l,c,a,e,t);else T(l,c,s,e,t)}const o=Object.values(t).reduce((n,l)=>(l&&n.push(...l),n),[]);return o.length>0?{valid:!1,errors:o,fieldErrors:t}:{valid:!0,data:e}}const Ce=[...D].reverse(),_=typeof window<"u";function B(e,r){for(const t of Ce){const o=e[t];if(o!==void 0&&!Number.isNaN(o)&&r>=o)return t}return"base"}function Me(e=L){const r=S(()=>JSON.stringify(e),[e]),t=N(e);t.current=e;const[o,n]=A(()=>_?B(e,window.innerWidth):"base");return b(()=>{if(!_)return;let l=[],s=[];const c=()=>{s.forEach(h=>h()),l=[],s=[];const a=t.current,u=B(a,window.innerWidth);n(u);const f=D.indexOf(u),d=D[f+1];if(d&&a[d]!==void 0){const h=a[d];if(Number.isNaN(h))throw new Error(`Invalid breakpoint value for ${d}: ${a[d]}`);{const y=window.matchMedia(`(min-width: ${h}px)`);l.push(y);const E=()=>c();y.addEventListener("change",E),s.push(()=>y.removeEventListener("change",E))}}const m=D[f-1];if(m&&a[m]!==void 0){const h=a[m];if(Number.isNaN(h))throw new Error(`Invalid breakpoint value for ${m}: ${a[m]}`);{const y=window.matchMedia(`(max-width: ${h-1}px)`);l.push(y);const E=()=>c();y.addEventListener("change",E),s.push(()=>y.removeEventListener("change",E))}}};return c(),()=>{s.forEach(a=>a())}},[r]),o}export{pe as ArrayRender,he as Boundary,Ne as Counter,ge as DateRender,L as DefBreakpointDesc,ne as False,w as If,ce as Observer,le as Pipe,de as Portal,fe as Repeat,ae as Scope,se as SizeBox,P as Styles,F as Switch,ue as Toggle,re as True,oe as When,D as breakpoints,j as childrenLoop,W as createExternalState,Fe as createStorageState,O as cx,be as formatDate,B as getCurrentBreakpoint,xe as ruleChecker,J as safePromiseTry,ve as safePromiseWithResolvers,Se as useControlled,Me as useScreen};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wwog/react",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.13",
|
|
4
4
|
"description": "A practical React component library providing declarative flow control and common UI utility components to make your React code more concise and readable.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
],
|
|
21
21
|
"homepage": "https://github.com/wwog/react",
|
|
22
22
|
"license": "MIT",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "unbuild",
|
|
25
|
+
"format": "biome format --write src",
|
|
26
|
+
"check": "biome check --write src",
|
|
27
|
+
"test:unit": "vitest run --browser.headless",
|
|
28
|
+
"test:types": "tsc --noEmit --skipLibCheck",
|
|
29
|
+
"all-suites": "pnpm run format && pnpm run check && pnpm run test:types && pnpm run test:unit",
|
|
30
|
+
"test:watch": "vitest"
|
|
31
|
+
},
|
|
23
32
|
"devDependencies": {
|
|
24
33
|
"@biomejs/biome": "^1.9.4",
|
|
25
34
|
"@types/react": "^19.1.2",
|
|
@@ -41,13 +50,5 @@
|
|
|
41
50
|
"node": ">= 20.0.0",
|
|
42
51
|
"pnpm": ">=8.15.0"
|
|
43
52
|
},
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
"format": "biome format --write src",
|
|
47
|
-
"check": "biome check --write src",
|
|
48
|
-
"test:unit": "vitest run --browser.headless",
|
|
49
|
-
"test:types": "tsc --noEmit --skipLibCheck",
|
|
50
|
-
"all-suites": "pnpm run format && pnpm run check && pnpm run test:types && pnpm run test:unit",
|
|
51
|
-
"test:watch": "vitest"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
53
|
+
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
|
|
54
|
+
}
|
|
@@ -42,8 +42,8 @@ h = (q + [13(m + 1)/5] + Y + [Y / 4] + 5) mod 7
|
|
|
42
42
|
* @returns 星期几(0=周日, 1=周一, ..., 6=周六)
|
|
43
43
|
*/
|
|
44
44
|
export function weekday(y: number, m: number, d: number): number {
|
|
45
|
-
const adjustedY = m < 3 ? y - 1 : y - 2
|
|
46
|
-
const adjustedD = d + adjustedY
|
|
45
|
+
const adjustedY = m < 3 ? y - 1 : y - 2
|
|
46
|
+
const adjustedD = d + adjustedY
|
|
47
47
|
return (
|
|
48
48
|
(Math.floor((23 * m) / 9) +
|
|
49
49
|
adjustedD +
|
|
@@ -52,7 +52,7 @@ export function weekday(y: number, m: number, d: number): number {
|
|
|
52
52
|
Math.floor(y / 100) +
|
|
53
53
|
Math.floor(y / 400)) %
|
|
54
54
|
7
|
|
55
|
-
)
|
|
55
|
+
)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -63,18 +63,13 @@ export function weekday(y: number, m: number, d: number): number {
|
|
|
63
63
|
* @returns 星期几(0=周六, 1=周日, ..., 6=周五)
|
|
64
64
|
*/
|
|
65
65
|
export function weekdayJulian(y: number, m: number, d: number): number {
|
|
66
|
-
const adjustedY = m < 3 ? y - 1 : y
|
|
67
|
-
const adjustedD = d + adjustedY
|
|
66
|
+
const adjustedY = m < 3 ? y - 1 : y
|
|
67
|
+
const adjustedD = d + adjustedY
|
|
68
68
|
if (m < 3) {
|
|
69
|
-
m += 12
|
|
70
|
-
y
|
|
69
|
+
m += 12
|
|
70
|
+
y--
|
|
71
71
|
}
|
|
72
72
|
return (
|
|
73
|
-
(adjustedD +
|
|
74
|
-
|
|
75
|
-
adjustedY +
|
|
76
|
-
Math.floor(adjustedY / 4) +
|
|
77
|
-
5) %
|
|
78
|
-
7
|
|
79
|
-
);
|
|
73
|
+
(adjustedD + Math.floor((13 * (m + 1)) / 5) + adjustedY + Math.floor(adjustedY / 4) + 5) % 7
|
|
74
|
+
)
|
|
80
75
|
}
|
package/src/algorithm/date.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from './constant'
|
|
2
|
+
export * from './Flex'
|
|
@@ -62,8 +62,8 @@ export const If = ({
|
|
|
62
62
|
} else {
|
|
63
63
|
throw new Error(
|
|
64
64
|
`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(
|
|
65
|
-
type.displayName || type.name || type
|
|
66
|
-
)}
|
|
65
|
+
type.displayName || type.name || type,
|
|
66
|
+
)}`,
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
69
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Fragment, type ReactNode } from "react";
|
|
1
|
+
import React, { Fragment, useRef, type ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
//#region component Types
|
|
4
4
|
export interface ArrayRenderProps<T> {
|
|
@@ -26,11 +26,11 @@ export function ArrayRender<T>(props: ArrayRenderProps<T>): ReactNode {
|
|
|
26
26
|
// 如果需要排序,先处理排序和过滤
|
|
27
27
|
if (sort) {
|
|
28
28
|
let processedItems = [...items];
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
if (filter) {
|
|
31
31
|
processedItems = processedItems.filter(filter);
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
processedItems = processedItems.sort(sort);
|
|
35
35
|
|
|
36
36
|
if (processedItems.length === 0) {
|
|
@@ -46,15 +46,20 @@ export function ArrayRender<T>(props: ArrayRenderProps<T>): ReactNode {
|
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
let nullCount = 0;
|
|
50
|
+
|
|
51
|
+
const renderItems = items.map((item, index) => {
|
|
52
|
+
if (filter && !filter(item)) {
|
|
53
|
+
nullCount++;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return renderItem(item, index);
|
|
57
|
+
})
|
|
58
|
+
|
|
49
59
|
// 如果不需要排序,保持原来的循环中过滤方式
|
|
50
60
|
return (
|
|
51
61
|
<Fragment>
|
|
52
|
-
{items.
|
|
53
|
-
if (filter && !filter(item)) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
return renderItem(item, index);
|
|
57
|
-
})}
|
|
62
|
+
{nullCount === items.length ? renderEmpty ? renderEmpty() : null : renderItems}
|
|
58
63
|
</Fragment>
|
|
59
64
|
);
|
|
60
65
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { Component, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface BoundaryProps {
|
|
4
|
+
/**
|
|
5
|
+
* @description_en Fallback UI to render when an error is caught. Receives the error and a reset function.
|
|
6
|
+
* @description_zh 捕获到错误时渲染的降级 UI,接收错误对象和重置函数。
|
|
7
|
+
*/
|
|
8
|
+
fallback: (error: Error, reset: () => void) => ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* @description_en Called when an error is caught, useful for logging.
|
|
11
|
+
* @description_zh 捕获到错误时的回调,可用于上报日志。
|
|
12
|
+
* @optional
|
|
13
|
+
*/
|
|
14
|
+
onError?: (error: Error, info: React.ErrorInfo) => void;
|
|
15
|
+
/**
|
|
16
|
+
* @description_en Child elements to protect.
|
|
17
|
+
* @description_zh 需要保护的子元素。
|
|
18
|
+
*/
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface BoundaryState {
|
|
23
|
+
error: Error | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class ErrorBoundaryCore extends Component<BoundaryProps, BoundaryState> {
|
|
27
|
+
state: BoundaryState = { error: null };
|
|
28
|
+
|
|
29
|
+
static getDerivedStateFromError(error: Error): BoundaryState {
|
|
30
|
+
return { error };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
|
34
|
+
this.props.onError?.(error, info);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
reset = () => {
|
|
38
|
+
this.setState({ error: null });
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
render() {
|
|
42
|
+
if (this.state.error) {
|
|
43
|
+
return this.props.fallback(this.state.error, this.reset);
|
|
44
|
+
}
|
|
45
|
+
return this.props.children;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @description_zh Error Boundary 的声明式封装,通过 render prop 提供降级 UI 和重置能力。
|
|
51
|
+
* @description_en Declarative Error Boundary wrapper with render-prop fallback and reset capability.
|
|
52
|
+
* @component
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* <Boundary fallback={(error, reset) => (
|
|
56
|
+
* <div>
|
|
57
|
+
* <p>出错了: {error.message}</p>
|
|
58
|
+
* <button onClick={reset}>重试</button>
|
|
59
|
+
* </div>
|
|
60
|
+
* )}>
|
|
61
|
+
* <RiskyComponent />
|
|
62
|
+
* </Boundary>
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function Boundary(props: BoundaryProps): ReactNode {
|
|
66
|
+
return <ErrorBoundaryCore {...props} />;
|
|
67
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState, type ReactNode } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
|
|
4
|
+
export interface PortalProps {
|
|
5
|
+
/**
|
|
6
|
+
* @description_en Target DOM element to mount into. Defaults to document.body.
|
|
7
|
+
* @description_zh 挂载目标 DOM 元素,默认为 document.body。
|
|
8
|
+
* @default document.body
|
|
9
|
+
*/
|
|
10
|
+
to?: Element | null;
|
|
11
|
+
/**
|
|
12
|
+
* @description_en Child elements to render into the portal.
|
|
13
|
+
* @description_zh 要渲染到 portal 中的子元素。
|
|
14
|
+
*/
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* @description_en Whether to disable the portal and render children inline. Default is false.
|
|
18
|
+
* @description_zh 是否禁用 portal,直接内联渲染子元素。默认为 false。
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @description_zh 声明式 Portal 组件,将子元素渲染到指定 DOM 节点,常用于模态框、浮层等场景。
|
|
26
|
+
* @description_en Declarative Portal component that renders children into a specified DOM node, commonly used for modals and overlays.
|
|
27
|
+
* @component
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <Portal>
|
|
31
|
+
* <Modal />
|
|
32
|
+
* </Portal>
|
|
33
|
+
*
|
|
34
|
+
* <Portal to={document.getElementById('overlay-root')}>
|
|
35
|
+
* <Tooltip />
|
|
36
|
+
* </Portal>
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function Portal({ to, children, disabled = false }: PortalProps): ReactNode {
|
|
40
|
+
const [mounted, setMounted] = useState(false);
|
|
41
|
+
// to 可能在首次渲染时为 null(SSR 或 ref 未就绪),延迟到客户端挂载后再渲染
|
|
42
|
+
const toRef = useRef(to);
|
|
43
|
+
toRef.current = to;
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
setMounted(true);
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
if (disabled) {
|
|
50
|
+
return <>{children}</>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!mounted) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const target = toRef.current ?? document.body;
|
|
58
|
+
return createPortal(children, target);
|
|
59
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { Fragment, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface RepeatProps {
|
|
4
|
+
/**
|
|
5
|
+
* @description_en Number of times to repeat.
|
|
6
|
+
* @description_zh 重复次数。
|
|
7
|
+
*/
|
|
8
|
+
times: number;
|
|
9
|
+
/**
|
|
10
|
+
* @description_en Render function receiving the current index (0-based).
|
|
11
|
+
* @description_zh 渲染函数,接收当前索引(从 0 开始)。
|
|
12
|
+
*/
|
|
13
|
+
children: (index: number) => ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @description_zh 声明式重复渲染组件,常用于骨架屏、占位符等场景。
|
|
18
|
+
* @description_en Declarative repeat-render component, commonly used for skeleton screens and placeholders.
|
|
19
|
+
* @component
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <Repeat times={3}>
|
|
23
|
+
* {(i) => <Skeleton key={i} />}
|
|
24
|
+
* </Repeat>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function Repeat({ times, children }: RepeatProps): ReactNode {
|
|
28
|
+
if (times <= 0) return null;
|
|
29
|
+
const items: ReactNode[] = [];
|
|
30
|
+
for (let i = 0; i < times; i++) {
|
|
31
|
+
items.push(children(i));
|
|
32
|
+
}
|
|
33
|
+
return <Fragment>{items}</Fragment>;
|
|
34
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
1
|
+
export * from './SizeBox'
|
|
2
|
+
export * from './Scope'
|
|
3
|
+
export * from './Styles'
|
|
4
|
+
export * from './Toggle'
|
|
5
|
+
export * from './Observer'
|
|
6
|
+
export * from './Repeat'
|
|
7
|
+
export * from './Portal'
|
|
8
|
+
export * from './Boundary'
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './useControlled'
|
|
2
|
-
export * from './useScreen'
|
|
2
|
+
export * from './useScreen'
|