@wwog/react 1.3.12 → 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 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: CreateStateListener<T>[];
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: "local" | "session";
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, 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 };
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 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 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.12",
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",
@@ -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'