@wwog/react 1.3.12 → 1.3.14

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 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.
@@ -571,6 +649,7 @@ const result = ruleChecker(registrationData, rules);
571
649
 
572
650
  > v1.2.21: Refactor the API to move sideeffects into options and enhance support for the transform interface
573
651
  > v1.2.13: add useGetter
652
+ > Breaking: `sideEffect` replaced by `onSet` and `onChange` for clearer callback semantics
574
653
 
575
654
  > A lightweight external state management utility that allows you to create and manage state outside the React component tree while maintaining perfect integration with components.
576
655
 
@@ -578,12 +657,18 @@ const result = ruleChecker(registrationData, rules);
578
657
 
579
658
  > Extends from createExternalState and uses storage to persist state, supports `localStorage` and `sessionStorage`
580
659
 
660
+ - `createStorageState<T>(key, initialState, options?)`: Creates persisted state
661
+ - `options.onSet`: Invoked on every `set()` (storage write happens first, then the user callback)
662
+ - `options.onChange`: Invoked only when the value actually changes
663
+ - `options.storageType`: `'local'` | `'session'`, defaults to `'local'`
664
+ - `options.transform`: Same as `createExternalState`
665
+
581
666
  ```tsx
582
667
  import { createExternalState } from "@wwog/react";
583
668
 
584
669
  // Create a global theme state
585
670
  const themeState = createExternalState("light", {
586
- sideEffect: (newTheme, oldTheme) => {
671
+ onChange: (newTheme, oldTheme) => {
587
672
  console.log(`Theme changed from ${oldTheme} to ${newTheme}`);
588
673
  },
589
674
  });
@@ -617,14 +702,17 @@ function ReadOnlyThemeConsumer() {
617
702
  - `createExternalState<T>(initialState, options?)`: Creates a state accessible outside components
618
703
 
619
704
  - `initialState`: Initial state value
620
- - `options.sideEffect`: Optional side effect function, called on state updates
705
+ - `options.onSet`: Optional callback invoked on every `set()` call, even when the value is unchanged
706
+ - `options.onChange`: Optional callback invoked only when the stored value actually changes (compared via `Object.is`)
707
+ - Both callbacks receive `(newState, prevState)` as the raw internal values (type `T`, before `transform.get`)
621
708
  - Returns an object with methods:
622
709
  - `get()`: Get the current state value
623
710
  - `set(newState)`: Update the state value
624
711
  - `use()`: React Hook, returns `[state, setState]` for using this state in components
625
712
  - `useGetter()`: React Hook that only returns the state value, useful when you only need to read the state
626
713
  - `options.transform`: - `get` - `set`
627
- Use cases:
714
+
715
+ Use cases:
628
716
 
629
717
  - Global state management (themes, user settings, etc.)
630
718
  - Cross-component communication
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;
@@ -541,19 +638,13 @@ interface UseControlledOptions<T> {
541
638
  declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
542
639
 
543
640
  /**
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
- /**
550
- * @zh 如果需要在变更状态时执行副作用,可以传入函数,对于异步函数,会在更改状态后执行,不会阻塞状态更新, 尽可能在外部使用useEffect处理异步副作用
551
- * @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.
641
+ * @zh 状态回调函数。对于异步函数,会在状态更新后执行,不会阻塞状态更新,尽可能在外部使用 useEffect 处理异步副作用。
642
+ * @en State callback function. Async callbacks run after the state update without blocking it; prefer useEffect for async side effects.
552
643
  * @template T The type of the state / 状态的类型
553
644
  * @param newState The new state value / 新的状态值
554
645
  * @param prevState The previous state value / 之前的状态值
555
646
  */
556
- type ExternalSideEffect<T> = (newState: T, prevState: T) => any | Promise<any>;
647
+ type ExternalStateCallback<T> = (newState: T, prevState: T) => any | Promise<any>;
557
648
  /**
558
649
  * @en Transform functions for getting and setting state
559
650
  * @zh 用于获取和设置状态的转换函数
@@ -580,10 +671,15 @@ interface Transform<T, U = T> {
580
671
  */
581
672
  interface ExternalStateOptions<T, U = T> {
582
673
  /**
583
- * @en Side effect function to run after state changes
584
- * @zh 状态变更后运行的副作用函数
674
+ * @en Callback invoked on every `set` call, even when the value is unchanged
675
+ * @zh 每次调用 `set` 后触发,即使值未发生变化
676
+ */
677
+ onSet?: ExternalStateCallback<T>;
678
+ /**
679
+ * @en Callback invoked only when the stored value actually changes
680
+ * @zh 仅在内部存储值发生变化时触发
585
681
  */
586
- sideEffect?: ExternalSideEffect<T>;
682
+ onChange?: ExternalStateCallback<T>;
587
683
  /**
588
684
  * @en Transform functions for getting and setting state
589
685
  * @zh 用于获取和设置状态的转换函数
@@ -612,7 +708,7 @@ interface ExternalState<T, U = T> {
612
708
  /**
613
709
  * @en React Hook for using external state in components
614
710
  * @zh 在组件中使用外部状态的 React Hook
615
- * @returns Array containing current钣金龙8国际唯一官网 current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
711
+ * @returns Array containing current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
616
712
  */
617
713
  use: () => [U, (newState: U | ((prevState: U) => U)) => void];
618
714
  /**
@@ -622,7 +718,7 @@ interface ExternalState<T, U = T> {
622
718
  useGetter: () => U;
623
719
  }
624
720
  interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
625
- __listeners: CreateStateListener<T>[];
721
+ __listeners: (() => void)[];
626
722
  }
627
723
  /**
628
724
  *
@@ -630,7 +726,7 @@ interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
630
726
  * ```tsx
631
727
  * // Create an app-level theme state with options
632
728
  * const themeState = createExternalState('light', {
633
- * sideEffect: (newState, prevState) => console.log(`Theme changed from ${prevState} to ${newState}`),
729
+ * onChange: (newState, prevState) => console.log(`Theme changed from ${prevState} to ${newState}`),
634
730
  * transform: {
635
731
  * get: (state) => state.toUpperCase(),
636
732
  * set: (value) => value.toLowerCase()
@@ -657,9 +753,10 @@ interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
657
753
  */
658
754
  declare function createExternalState<T, U = T>(initialState: T | (() => T), options?: ExternalStateOptions<T, U>): ExternalState<T, U>;
659
755
  interface StorageStateOptions<T, U> {
660
- sideEffect?: (newState: T) => void;
756
+ onSet?: ExternalStateCallback<T>;
757
+ onChange?: ExternalStateCallback<T>;
661
758
  transform?: Transform<T, U>;
662
- storageType: "local" | "session";
759
+ storageType: 'local' | 'session';
663
760
  }
664
761
  declare function createStorageState<T, U = T>(key: string, initialState: T, options?: StorageStateOptions<T, U>): ExternalState<T, U>;
665
762
 
@@ -774,5 +871,5 @@ declare function ruleChecker<T extends Record<string, unknown>, R extends RuleDe
774
871
  declare function getCurrentBreakpoint(breakpointDesc: BreakpointDesc, width: number): BreakpointName;
775
872
  declare function useScreen(breakpointDesc?: BreakpointDesc): "base" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
776
873
 
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, CreateStateListener, CxInput, DateRenderProps, ElseIfProps, ElseProps, ExternalSideEffect, ExternalState, ExternalStateOptions, ExternalWithKernel, FalseProps, FieldRule, IfProps, LengthRuleProps, NumberRangeProps, NumberRule, ObserverProps, PipeProps, Responsive, RuleDescription, ScopeProps, StorageStateOptions, StringRule, StringSpecificProps, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, Transform, TrueProps, UseControlledOptions, WhenProps };
874
+ 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 };
875
+ export type { ApplyRules, ArrayRenderProps, ArrayRule, ArraySpecificProps, BaseRule, BooleanRule, BoundaryProps, BreakpointDesc, BreakpointName, CxInput, DateRenderProps, ElseIfProps, ElseProps, ExternalState, ExternalStateCallback, 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 i,{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=>i.createElement(i.Fragment,null,e.children);N.displayName="Switch_Case";const D=e=>i.createElement(i.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 a=null,c=null,u=!1;return j(o,(s,f)=>{if(!i.isValidElement(s))throw new Error(`Switch Children only accepts valid React elements at index ${f}`);const d=s.type;if(d.displayName===N.displayName){const m=s.props;if(l.has(m.value))throw new Error(`Switch found duplicate Case value at index ${f}: ${JSON.stringify(m.value)}${r?" (detected in strict mode)":""}`);if(l.add(m.value),!a&&t(n,m.value)&&(a=m.children,r===!1))return!1}else if(d.displayName===D.displayName){if(u)throw new Error(`Switch can only have one Default child at index ${f}`);if(u=!0,c=s.props.children,!r&&a)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,a??c)};w.displayName="Switch",w.Case=N,w.Default=D,w.createTyped=function(){return{Switch:w,Case:N,Default:D}};const A=e=>i.createElement(i.Fragment,null,e.children),x=({children:e})=>i.createElement(i.Fragment,null,e),M=e=>i.createElement(i.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(i.Children.forEach(n,l=>{if(!i.isValidElement(l))throw new Error("If component only accepts valid React elements");const a=l.type;if(a.displayName===A.displayName){if(t)throw new Error("If component can only have one Then child");t=l}else if(a.displayName===M.displayName)r.push(l);else if(a.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(a.displayName||a.name||a)}`)}),e)return t?i.createElement(i.Fragment,null,t.props.children):null;for(const l of r)if(l.props.condition)return i.createElement(i.Fragment,null,l.props.children);return o?i.createElement(i.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?i.createElement(i.Fragment,null,n):null,X=({condition:e,children:n})=>e===!1?i.createElement(i.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])?i.createElement(i.Fragment,null,o):i.createElement(i.Fragment,null,r||null),te=({data:e,transform:n,render:t,fallback:o})=>{const r=S(()=>n.reduce((l,a)=>a(l),e),[e,n]);return r==null?i.createElement(i.Fragment,null,o||null):i.createElement(i.Fragment,null,t(r))},ne=e=>{const{children:n,h:t,w:o,size:r,height:l,width:a,className:c}=e;return i.createElement("div",{style:{width:r||o||a,height:r||t||l,flexShrink:0},className:c},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?i.createElement(i.Fragment,null,o||null):i.createElement(i.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 i.createElement(v,null,n);const o=typeof e=="string"?e:C(...Object.values(e));if(t)return i.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."),i.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."),i.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,a]=I(n),c=()=>{a(u=>t.length?o?o(u,t):(u+1)%t.length:u)};return r(t[l],c)},se=({onIntersect:e,threshold:n=.1,root:t=null,rootMargin:o="0px",triggerOnce:r=!1,disabled:l=!1,children:a,className:c,style:u})=>{const s=O(null),f=O(null),d=O(!1);return b(()=>{if(l||!s.current)return;if(!window.IntersectionObserver){console.warn("IntersectionObserver is not supported in this browser");return}const m=s.current,g=y=>{y.forEach($=>{r&&d.current||(e($,f.current),r&&(d.current=!0,f.current?.unobserve(m)))})};return f.current=new IntersectionObserver(g,{root:t,rootMargin:o,threshold:n}),f.current.observe(m),()=>{f.current&&f.current.disconnect()}},[e,n,t,o,r,l]),b(()=>{r||(d.current=!1)},[r]),i.createElement("div",{ref:s,className:c,style:u},a)};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 u=[...n];return o&&(u=u.filter(o)),u=u.sort(l),u.length===0?r?r():null:i.createElement(v,null,u.map((s,f)=>t(s,f)))}let a=0;const c=n.map((u,s)=>o&&!o(u)?(a++,null):t(u,s));return i.createElement(v,null,a===n.length?r?r():null:c)}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:i.createElement(i.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,a=Object.prototype.hasOwnProperty.call(l,r),[c,u]=I(n),s=a?l[r]:c,f=S(()=>l[o],[l,o]),d=U(m=>{const g=typeof m=="function"?m(s):m;t&&t(g,s)===!1||(a||u(g),f&&f(g))},[a,t,s,f]);return[s,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,a=()=>{const s=t;return l?.get?l.get(s):s},c=s=>{const f=t,d=l?.get?l.get(f):f;t=l?.set?l.set(typeof s=="function"?s(d):s):typeof s=="function"?s(d):s,o.forEach(m=>m(t)),r&&J(r,t,f).catch(m=>{console.error("Error in external state side effect, Please do it within side effects:",m)})},u=()=>{const[s,f]=i.useState(t);return i.useEffect(()=>(o.push(f),()=>{const d=o.indexOf(f);d>-1&&o.splice(d,1)}),[]),[l?.get?l.get(s):s,c]};return{get:a,set:c,use:u,useGetter:()=>{const[s]=u();return s},__listeners:o}}function he(e,n,t){const{storageType:o="local",sideEffect:r,transform:l}=t??{};let a=n;if(typeof window<"u"){const c=(o==="local"?localStorage:sessionStorage).getItem(e);if(c)try{a=JSON.parse(c)}catch(u){console.warn(`Failed to parse ${o}Storage value for key "${e}", using initial state:`,u),a=n}}return W(a,{sideEffect:c=>{typeof window<"u"&&(o==="local"?localStorage:sessionStorage).setItem(e,JSON.stringify(c)),r?.(c)},transform:l})}function pe(e,n){const t=n||new Date,o=t.getFullYear(),r=t.getMonth()+1,l=t.getDate(),a=t.getHours(),c=t.getMinutes(),u=t.getSeconds(),s=t.getMilliseconds(),f=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[f],Y=d[f],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:f.toString(),dd:Y,ddd:Y,dddd:$,H:a.toString(),HH:a.toString().padStart(2,"0"),h:(a%12).toString(),hh:(a%12).toString().padStart(2,"0"),m:c.toString(),mm:c.toString().padStart(2,"0"),s:u.toString(),ss:u.toString().padStart(2,"0"),SSS:s.toString().padStart(3,"0"),Z:"+08:00",ZZ:"+0800",A:a<12?"AM":"PM",a:a<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 h(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 p(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),a=Ee(n);if(t.required&&!l){h(r,e,t.message??p(e,"\u4E3A\u5FC5\u586B\u9879"));return}if(!(!a&&!t.required)){if(t.dependsOn){const c=t.dependsOn(o);c===!1?h(r,e,t.message??p(e,"\u4F9D\u8D56\u6761\u4EF6\u672A\u6EE1\u8DB3")):typeof c=="string"&&h(r,e,c)}if(typeof n=="string"){const c=t,{len:u,min:s,max:f,regex:d,email:m,url:g,phone:y}=c;typeof u=="number"&&n.length!==u&&h(r,e,t.message??p(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${u}`)),typeof s=="number"&&n.length<s&&h(r,e,t.message??p(e,`\u957F\u5EA6\u4E0D\u80FD\u5C11\u4E8E ${s}`)),typeof f=="number"&&n.length>f&&h(r,e,t.message??p(e,`\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${f}`)),d&&!d.test(n)&&h(r,e,t.message??p(e,"\u683C\u5F0F\u4E0D\u6B63\u786E")),m&&!T.email.test(n)&&h(r,e,t.message??p(e,"\u4E0D\u662F\u6709\u6548\u7684\u90AE\u7BB1")),g&&!T.url.test(n)&&h(r,e,t.message??p(e,"\u4E0D\u662F\u6709\u6548\u7684URL")),y&&!T.phone.test(n)&&h(r,e,t.message??p(e,"\u4E0D\u662F\u6709\u6548\u7684\u624B\u673A\u53F7"))}if(typeof n=="number"){const c=t,{min:u,max:s}=c;typeof u=="number"&&n<u&&h(r,e,t.message??p(e,`\u4E0D\u80FD\u5C0F\u4E8E ${u}`)),typeof s=="number"&&n>s&&h(r,e,t.message??p(e,`\u4E0D\u80FD\u5927\u4E8E ${s}`))}if(Array.isArray(n)){const c=t,{len:u,min:s,max:f,unique:d,elementRule:m}=c;typeof u=="number"&&n.length!==u&&h(r,e,t.message??p(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${u}`)),typeof s=="number"&&n.length<s&&h(r,e,t.message??p(e,`\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E ${s}`)),typeof f=="number"&&n.length>f&&h(r,e,t.message??p(e,`\u957F\u5EA6\u4E0D\u80FD\u5927\u4E8E ${f}`)),d&&new Set(n).size!==n.length&&h(r,e,t.message??p(e,"\u5143\u7D20\u5FC5\u987B\u552F\u4E00")),m&&n.forEach((g,y)=>{k(`${String(e)}[${y}]`,g,m,o,r)})}if(t.validator){const c=t.validator?.(n,o);c===!1?h(r,e,t.message??p(e,"\u6821\u9A8C\u672A\u901A\u8FC7")):typeof c=="string"&&h(r,e,c)}}}function Se(e,n){const t={};for(const r in n){const l=r,a=n[l];if(!a)continue;const c=e[l];if(Array.isArray(a))for(const u of a)k(l,c,u,e,t);else k(l,c,a,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(f=>f()),o=[],r=[];const a=B(e,window.innerWidth);t(a);const c=F.indexOf(a),u=F[c+1];if(u&&e[u]!==void 0){const f=e[u];if(Number.isNaN(f))throw new Error(`Invalid breakpoint value for ${u}: ${e[u]}`);{const d=window.matchMedia(`(min-width: ${f}px)`);o.push(d);const m=()=>l();d.addEventListener("change",m),r.push(()=>d.removeEventListener("change",m))}}const s=F[c-1];if(s&&e[s]!==void 0){const f=e[s];if(Number.isNaN(f))throw new Error(`Invalid breakpoint value for ${s}: ${e[s]}`);{const d=window.matchMedia(`(max-width: ${f-1}px)`);o.push(d);const m=()=>l();d.addEventListener("change",m),r.push(()=>d.removeEventListener("change",m))}}};return l(),()=>{r.forEach(a=>a())}},[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,he as createStorageState,C as cx,pe as formatDate,B as getCurrentBreakpoint,Se as ruleChecker,J as safePromiseTry,me as safePromiseWithResolvers,fe as useControlled,ve as useScreen};
1
+ import u,{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,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 te=(e,n)=>e===n,x=e=>u.createElement(u.Fragment,null,e.children);x.displayName="Switch_Case";const C=e=>u.createElement(u.Fragment,null,e.children);C.displayName="Switch_Default";const F=e=>{const{value:n,compare:t=te,children:o,strict:r=!1}=e,l=new Set;let s=null,i=null,a=!1;return j(o,(f,m)=>{if(!u.isValidElement(f))throw new Error(`Switch Children only accepts valid React elements at index ${m}`);const c=f.type;if(c.displayName===x.displayName){const d=f.props;if(l.has(d.value))throw new Error(`Switch found duplicate Case value at index ${m}: ${JSON.stringify(d.value)}${r?" (detected in strict mode)":""}`);if(l.add(d.value),!s&&t(n,d.value)&&(s=d.children,r===!1))return!1}else if(c.displayName===C.displayName){if(a)throw new Error(`Switch can only have one Default child at index ${m}`);if(a=!0,i=f.props.children,!r&&s)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(c.displayName||c.name||c)} at index ${m}`)}),u.createElement(u.Fragment,null,s??i)};F.displayName="Switch",F.Case=x,F.Default=C,F.createTyped=function(){return{Switch:F,Case:x,Default:C}};const M=e=>u.createElement(u.Fragment,null,e.children),I=({children:e})=>u.createElement(u.Fragment,null,e),O=e=>u.createElement(u.Fragment,null,e.children);M.displayName="If_Then",I.displayName="If_Else",O.displayName="If_ElseIf";const w=({condition:e,children:n})=>{let t=null,o=null;const r=[];if(u.Children.forEach(n,l=>{if(!u.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===O.displayName)r.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?u.createElement(u.Fragment,null,t.props.children):null;for(const l of r)if(l.props.condition)return u.createElement(u.Fragment,null,l.props.children);return o?u.createElement(u.Fragment,null,o.props.children):null};w.displayName="If",w.Then=M,w.ElseIf=O,w.Else=I,w.createTyped=function(){return{If:w,Then:M,ElseIf:O,Else:I}};const ne=({condition:e,children:n})=>e?u.createElement(u.Fragment,null,n):null,re=({condition:e,children:n})=>e===!1?u.createElement(u.Fragment,null,n):null,oe=({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])?u.createElement(u.Fragment,null,o):u.createElement(u.Fragment,null,r||null),le=({data:e,transform:n,render:t,fallback:o})=>{const r=S(()=>n.reduce((l,s)=>s(l),e),[e,n]);return r==null?u.createElement(u.Fragment,null,o||null):u.createElement(u.Fragment,null,t(r))},se=e=>{const{children:n,h:t,w:o,size:r,height:l,width:s,className:i}=e;return u.createElement("div",{style:{width:r||o||s,height:r||t||l,flexShrink:0},className:i},n)},ae=({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?u.createElement(u.Fragment,null,o||null):u.createElement(u.Fragment,null,t(r))};function $(...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 ue=e=>typeof e=="object"&&!!e,P=({className:e,children:n,asWrapper:t=!1})=>{if(!n)return null;if(!e)return u.createElement(v,null,n);const o=typeof e=="string"?e:$(...Object.values(e));if(t)return u.createElement(t===!0?"div":t,{className:o},n);if(q.count(n)>1)return console.error("<Styles>: children has more than one child. Please check your code."),u.createElement(v,null,n);if(U(n)){const r=n;let l=r?.props?.className;return r?.type?.displayName===P.displayName&&ue(l)&&(l=$(...Object.values(l))),G(n,{className:$(o,l)})}return console.error("<Styles>: children is not a valid React element. Please check your code."),u.createElement(v,null,n)};P.displayName="W/Styles";const ie=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]=A(n),i=()=>{s(a=>t.length?o?o(a,t):(a+1)%t.length:a)};return r(t[l],i)},ce=({onIntersect:e,threshold:n=.1,root:t=null,rootMargin:o="0px",triggerOnce:r=!1,disabled:l=!1,children:s,className:i,style:a})=>{const f=N(null),m=N(null),c=N(!1);return b(()=>{if(l||!f.current)return;if(!window.IntersectionObserver){console.warn("IntersectionObserver is not supported in this browser");return}const d=f.current,h=p=>{p.forEach(E=>{r&&c.current||(e(E,m.current),r&&(c.current=!0,m.current?.unobserve(d)))})};return m.current=new IntersectionObserver(h,{root:t,rootMargin:o,threshold:n}),m.current.observe(d),()=>{m.current&&m.current.disconnect()}},[e,n,t,o,r,l]),b(()=>{r||(c.current=!1)},[r]),u.createElement("div",{ref:f,className:i,style:a},s)};function fe({times:e,children:n}){if(e<=0)return null;const t=[];for(let o=0;o<e;o++)t.push(n(o));return u.createElement(v,null,t)}function de({to:e,children:n,disabled:t=!1}){const[o,r]=A(!1),l=N(e);if(l.current=e,b(()=>{r(!0)},[]),t)return u.createElement(u.Fragment,null,n);if(!o)return null;const s=l.current??document.body;return ee(n,s)}class me extends K{state={error:null};static getDerivedStateFromError(n){return{error:n}}componentDidCatch(n,t){this.props.onError?.(n,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 u.createElement(me,{...e})}function pe(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 a=[...n];return o&&(a=a.filter(o)),a=a.sort(l),a.length===0?r?r():null:u.createElement(v,null,a.map((f,m)=>t(f,m)))}let s=0;const i=n.map((a,f)=>o&&!o(a)?(s++,null):t(a,f));return u.createElement(v,null,s===n.length?r?r():null:i)}function ge({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:u.createElement(u.Fragment,null,t(r))}const ye="onChange",Ee="value";function Se(e){const{defaultValue:n,onBeforeChange:t,trigger:o=ye,valuePropName:r=Ee,props:l}=e,s=Object.prototype.hasOwnProperty.call(l,r),[i,a]=A(n),f=s?l[r]:i,m=S(()=>l[o],[l,o]),c=Q(d=>{const h=typeof d=="function"?d(f):d;t&&t(h,f)===!1||(s||a(h),m&&m(h))},[s,t,f,m]);return[f,c]}function we(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):we,ve=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=[],{onSet:r,onChange:l,transform:s}=n,i=(c,d,h)=>{c&&J(c,d,h).catch(p=>{console.error("Error in external state callback, Please do it within side effects:",p)})},a=()=>{const c=t;return s?.get?s.get(c):c},f=c=>{const d=t,h=s?.get?s.get(d):d;t=s?.set?s.set(typeof c=="function"?c(h):c):typeof c=="function"?c(h):c,o.forEach(p=>p()),i(r,t,d),Object.is(t,d)||i(l,t,d)},m=()=>{const c=X(d=>(o.push(d),()=>{const h=o.indexOf(d);h>-1&&o.splice(h,1)}),()=>t,()=>t);return[s?.get?s.get(c):c,f]};return{get:a,set:f,use:m,useGetter:()=>{const[c]=m();return c},__listeners:o}}function Fe(e,n,t){const{storageType:o="local",onSet:r,onChange:l,transform:s}=t??{};let i=n;if(typeof window<"u"){const a=(o==="local"?localStorage:sessionStorage).getItem(e);if(a)try{i=JSON.parse(a)}catch(f){console.warn(`Failed to parse ${o}Storage value for key "${e}", using initial state:`,f),i=n}}return W(i,{onSet:(a,f)=>{typeof window<"u"&&(o==="local"?localStorage:sessionStorage).setItem(e,JSON.stringify(a)),r?.(a,f)},onChange:l,transform:s})}function be(e,n){const t=n||new Date,o=t.getFullYear(),r=t.getMonth()+1,l=t.getDate(),s=t.getHours(),i=t.getMinutes(),a=t.getSeconds(),f=t.getMilliseconds(),m=t.getDay(),c=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],d=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],h=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],p=["January","February","March","April","May","June","July","August","September","October","November","December"],E=d[m],R=c[m],Y=r-1,H=p[Y],z=h[Y],V={YY:o.toString().slice(2),YYYY:o.toString(),M:r.toString(),MM:r.toString().padStart(2,"0"),MMM:z,MMMM:H,D:l.toString(),DD:l.toString().padStart(2,"0"),d:m.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:i.toString(),mm:i.toString().padStart(2,"0"),s:a.toString(),ss:a.toString().padStart(2,"0"),SSS:f.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 g(e,n,t){t&&(e[n]||(e[n]=[]),e[n].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 y(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=De(n),s=Ae(n);if(t.required&&!l){g(r,e,t.message??y(e,"\u4E3A\u5FC5\u586B\u9879"));return}if(!(!s&&!t.required)){if(t.dependsOn){const i=t.dependsOn(o);i===!1?g(r,e,t.message??y(e,"\u4F9D\u8D56\u6761\u4EF6\u672A\u6EE1\u8DB3")):typeof i=="string"&&g(r,e,i)}if(typeof n=="string"){const i=t,{len:a,min:f,max:m,regex:c,email:d,url:h,phone:p}=i;typeof a=="number"&&n.length!==a&&g(r,e,t.message??y(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${a}`)),typeof f=="number"&&n.length<f&&g(r,e,t.message??y(e,`\u957F\u5EA6\u4E0D\u80FD\u5C11\u4E8E ${f}`)),typeof m=="number"&&n.length>m&&g(r,e,t.message??y(e,`\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${m}`)),c&&!c.test(n)&&g(r,e,t.message??y(e,"\u683C\u5F0F\u4E0D\u6B63\u786E")),d&&!T.email.test(n)&&g(r,e,t.message??y(e,"\u4E0D\u662F\u6709\u6548\u7684\u90AE\u7BB1")),h&&!T.url.test(n)&&g(r,e,t.message??y(e,"\u4E0D\u662F\u6709\u6548\u7684URL")),p&&!T.phone.test(n)&&g(r,e,t.message??y(e,"\u4E0D\u662F\u6709\u6548\u7684\u624B\u673A\u53F7"))}if(typeof n=="number"){const i=t,{min:a,max:f}=i;typeof a=="number"&&n<a&&g(r,e,t.message??y(e,`\u4E0D\u80FD\u5C0F\u4E8E ${a}`)),typeof f=="number"&&n>f&&g(r,e,t.message??y(e,`\u4E0D\u80FD\u5927\u4E8E ${f}`))}if(Array.isArray(n)){const i=t,{len:a,min:f,max:m,unique:c,elementRule:d}=i;typeof a=="number"&&n.length!==a&&g(r,e,t.message??y(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${a}`)),typeof f=="number"&&n.length<f&&g(r,e,t.message??y(e,`\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E ${f}`)),typeof m=="number"&&n.length>m&&g(r,e,t.message??y(e,`\u957F\u5EA6\u4E0D\u80FD\u5927\u4E8E ${m}`)),c&&new Set(n).size!==n.length&&g(r,e,t.message??y(e,"\u5143\u7D20\u5FC5\u987B\u552F\u4E00")),d&&n.forEach((h,p)=>{k(`${String(e)}[${p}]`,h,d,o,r)})}if(t.validator){const i=t.validator?.(n,o);i===!1?g(r,e,t.message??y(e,"\u6821\u9A8C\u672A\u901A\u8FC7")):typeof i=="string"&&g(r,e,i)}}}function xe(e,n){const t={};for(const r in n){const l=r,s=n[l];if(!s)continue;const i=e[l];if(Array.isArray(s))for(const a of s)k(l,i,a,e,t);else k(l,i,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 Ce=[...D].reverse(),_=typeof window<"u";function B(e,n){for(const t of Ce){const o=e[t];if(o!==void 0&&!Number.isNaN(o)&&n>=o)return t}return"base"}function Me(e=L){const n=S(()=>JSON.stringify(e),[e]),t=N(e);t.current=e;const[o,r]=A(()=>_?B(e,window.innerWidth):"base");return b(()=>{if(!_)return;let l=[],s=[];const i=()=>{s.forEach(h=>h()),l=[],s=[];const a=t.current,f=B(a,window.innerWidth);r(f);const m=D.indexOf(f),c=D[m+1];if(c&&a[c]!==void 0){const h=a[c];if(Number.isNaN(h))throw new Error(`Invalid breakpoint value for ${c}: ${a[c]}`);{const p=window.matchMedia(`(min-width: ${h}px)`);l.push(p);const E=()=>i();p.addEventListener("change",E),s.push(()=>p.removeEventListener("change",E))}}const d=D[m-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 p=window.matchMedia(`(max-width: ${h-1}px)`);l.push(p);const E=()=>i();p.addEventListener("change",E),s.push(()=>p.removeEventListener("change",E))}}};return i(),()=>{s.forEach(a=>a())}},[n]),o}export{pe as ArrayRender,he as Boundary,Ne as Counter,ge as DateRender,L as DefBreakpointDesc,re 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,ie as Toggle,ne as True,oe as When,D as breakpoints,j as childrenLoop,W as createExternalState,Fe as createStorageState,$ 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.12",
3
+ "version": "1.3.14",
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",
@@ -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
- Math.floor((13 * (m + 1)) / 5) +
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
  }
@@ -1,5 +1,5 @@
1
- import * as zllersKongruenz from "./date/zellersKongruenz";
1
+ import * as zllersKongruenz from './date/zellersKongruenz'
2
2
 
3
3
  export const weekday = {
4
4
  zllersKongruenz,
5
- };
5
+ }
@@ -1,2 +1,2 @@
1
- export * from "./constant";
2
- export * from "./Flex";
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
  });
@@ -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 "./SizeBox";
2
- export * from "./Scope";
3
- export * from "./Styles";
4
- export * from "./Toggle";
5
- export * from "./Observer";
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'
@@ -1,2 +1,2 @@
1
1
  export * from './useControlled'
2
- export * from './useScreen'
2
+ export * from './useScreen'