@wwog/react 1.3.6 → 1.3.9

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
@@ -167,7 +167,7 @@ import { Toggle } from "@wwog/react";
167
167
 
168
168
  #### `<ArrayRender>`
169
169
 
170
- Efficiently render array data, supports filtering and custom rendering.
170
+ Efficiently render array data, supports filtering, sorting, and custom rendering. Optimized for performance with minimal loops.
171
171
 
172
172
  ```tsx
173
173
  import { ArrayRender } from "@wwog/react";
@@ -177,16 +177,27 @@ function UserList({ users }) {
177
177
  <ArrayRender
178
178
  items={users}
179
179
  filter={(user) => user.active}
180
+ sort={(a, b) => a.name.localeCompare(b.name)}
180
181
  renderItem={(user, index) => (
181
182
  <div key={user.id}>
182
183
  {index + 1}. {user.name}
183
184
  </div>
184
185
  )}
186
+ renderEmpty={() => <div>No users found</div>}
185
187
  />
186
188
  );
187
189
  }
188
190
  ```
189
191
 
192
+ - `items`: Array of data to render
193
+ - `renderItem`: Function to render each item, receives (item, index) as parameters
194
+ - `filter`: Optional filter function to filter items
195
+ - `sort`: Optional sort function for array sorting, uses standard comparison function (a, b) => number
196
+ - `renderEmpty`: Optional function to render content when array is empty
197
+
198
+ **Performance Note**: When no sorting is needed, filtering is done during the map loop for optimal performance. When sorting is provided, filtering is applied first, then sorting, to minimize operations.
199
+ ```
200
+
190
201
  #### `<Clamp>` (v1.2.14+)
191
202
 
192
203
  > Removed in v1.3.0. The compatibility problem is too big, the desktop web page works well, h5 has a problem.
@@ -466,6 +477,96 @@ Development notes: Internally implemented via `mediaQuery`, it does not listen t
466
477
 
467
478
  > Internal functions used by some components, which can also be used if needed
468
479
 
480
+ #### `ruleChecker` (v1.3.9+)
481
+
482
+ A type-safe data validation utility that provides comprehensive validation rules for different data types with full TypeScript support.
483
+
484
+ ```tsx
485
+ import { ruleChecker } from "@wwog/react";
486
+
487
+ // Define your data and validation rules
488
+ const userData = {
489
+ username: 'john',
490
+ email: 'john@example.com',
491
+ age: 25,
492
+ hobbies: ['reading', 'coding']
493
+ };
494
+
495
+ const validationRules = {
496
+ username: { required: true, min: 3, max: 20 },
497
+ email: { required: true, email: true },
498
+ age: { required: true, min: 18, max: 120 },
499
+ hobbies: { min: 1, max: 5, unique: true }
500
+ };
501
+
502
+ // Validate the data
503
+ const result = ruleChecker(userData, validationRules);
504
+
505
+ if (result.valid) {
506
+ console.log('Data is valid:', result.data);
507
+ } else {
508
+ console.log('Validation errors:', result.errors);
509
+ console.log('Field-specific errors:', result.fieldErrors);
510
+ }
511
+ ```
512
+
513
+ **Features:**
514
+ - **Type-safe**: Full TypeScript support with automatic type inference
515
+ - **Multiple data types**: Support for strings, numbers, booleans, and arrays
516
+ - **Comprehensive rules**: Built-in validation for length, range, format, uniqueness, etc.
517
+ - **Custom validators**: Support for custom validation functions
518
+ - **Dependency validation**: Validate fields based on other field values
519
+ - **Array element validation**: Validate individual elements within arrays
520
+ - **Multiple rule support**: Apply multiple validation rules to a single field
521
+ - **Detailed error reporting**: Get both general errors and field-specific errors
522
+
523
+ **Available Rules:**
524
+ - **Common rules**: `required`, `message`, `validator`, `dependsOn`
525
+ - **String rules**: `min`, `max`, `len`, `regex`, `email`, `url`, `phone`
526
+ - **Number rules**: `min`, `max`
527
+ - **Array rules**: `min`, `max`, `len`, `unique`, `elementRule`
528
+ - **Boolean rules**: Basic validation with custom validators
529
+
530
+ **Complex Example:**
531
+ ```tsx
532
+ const registrationData = {
533
+ username: 'user123',
534
+ email: 'user@example.com',
535
+ password: 'SecurePass123',
536
+ confirmPassword: 'SecurePass123',
537
+ age: 25,
538
+ tags: ['developer', 'typescript'],
539
+ terms: true
540
+ };
541
+
542
+ const rules = {
543
+ username: { required: true, min: 3, max: 20, regex: /^[a-zA-Z0-9_]+$/ },
544
+ email: { required: true, email: true },
545
+ password: [
546
+ { required: true, min: 8 },
547
+ { regex: /[A-Z]/, message: 'Password must contain uppercase letter' },
548
+ { regex: /[0-9]/, message: 'Password must contain number' }
549
+ ],
550
+ confirmPassword: {
551
+ required: true,
552
+ validator: (value, data) => value === data.password || 'Passwords do not match'
553
+ },
554
+ age: { required: true, min: 18, max: 120 },
555
+ tags: {
556
+ min: 1,
557
+ max: 10,
558
+ unique: true,
559
+ elementRule: { min: 2, max: 20 } // Each tag must be 2-20 characters
560
+ },
561
+ terms: {
562
+ required: true,
563
+ validator: (value) => value === true || 'You must accept the terms'
564
+ }
565
+ };
566
+
567
+ const result = ruleChecker(registrationData, rules);
568
+ ```
569
+
469
570
  #### `createExternalState` (v1.2.9+, useGetter added in v1.2.13)
470
571
 
471
572
  > v1.2.21: Refactor the API to move sideeffects into options and enhance support for the transform interface
package/dist/index.d.mts CHANGED
@@ -465,6 +465,8 @@ interface ArrayRenderProps<T> {
465
465
  items: T[];
466
466
  renderItem: (item: T, index: number) => React$1.ReactNode;
467
467
  filter?: (item: T) => boolean;
468
+ renderEmpty?: () => React$1.ReactNode;
469
+ sort?: (a: T, b: T) => number;
468
470
  }
469
471
  declare function ArrayRender<T>(props: ArrayRenderProps<T>): ReactNode;
470
472
 
@@ -714,8 +716,58 @@ type BreakpointName = (typeof breakpoints)[number];
714
716
  type BreakpointDesc = Partial<Record<BreakpointName, number>>;
715
717
  type Responsive<T> = T | Partial<Record<BreakpointName, T>>;
716
718
 
719
+ type BaseRule<TAll, TValue> = {
720
+ required?: boolean;
721
+ message?: string;
722
+ validator?: (value: TValue, data: Partial<TAll>) => boolean | string;
723
+ dependsOn?: (data: Partial<TAll>) => boolean | string;
724
+ };
725
+ type LengthRuleProps = {
726
+ min?: number;
727
+ max?: number;
728
+ len?: number;
729
+ };
730
+ type NumberRangeProps = {
731
+ min?: number;
732
+ max?: number;
733
+ };
734
+ type StringSpecificProps = {
735
+ regex?: RegExp;
736
+ email?: boolean;
737
+ url?: boolean;
738
+ phone?: boolean;
739
+ };
740
+ type ArraySpecificProps = {
741
+ unique?: boolean;
742
+ };
743
+ type NumberRule<TAll> = BaseRule<TAll, number> & NumberRangeProps;
744
+ type StringRule<TAll> = BaseRule<TAll, string> & LengthRuleProps & StringSpecificProps;
745
+ type BooleanRule<TAll> = BaseRule<TAll, boolean>;
746
+ type ArrayRule<TAll, U> = BaseRule<TAll, U[]> & LengthRuleProps & ArraySpecificProps & {
747
+ elementRule?: FieldRule<U, TAll>;
748
+ };
749
+ type FieldRule<TValue, TAll> = TValue extends string ? StringRule<TAll> : TValue extends number ? NumberRule<TAll> : TValue extends boolean ? BooleanRule<TAll> : TValue extends (infer U)[] ? ArrayRule<TAll, U> : BaseRule<TAll, TValue>;
750
+ type RuleDescription<T extends Record<string, unknown>> = {
751
+ [K in keyof T]?: FieldRule<T[K], T> | FieldRule<T[K], T>[];
752
+ };
753
+ type IsRequired<R> = R extends {
754
+ required: true;
755
+ } ? true : false;
756
+ type ApplyRules<T extends Record<string, unknown>, R extends RuleDescription<T>> = {
757
+ [K in keyof T]: IsRequired<R[K]> extends true ? T[K] : T[K] | undefined;
758
+ };
759
+ type FieldErrors<T> = Partial<Record<keyof T, string[]>>;
760
+ declare function ruleChecker<T extends Record<string, unknown>, R extends RuleDescription<T>>(data: Partial<T>, rules: R): {
761
+ valid: true;
762
+ data: ApplyRules<T, R>;
763
+ } | {
764
+ valid: false;
765
+ errors: string[];
766
+ fieldErrors: FieldErrors<T>;
767
+ };
768
+
717
769
  declare function getCurrentBreakpoint(breakpointDesc: BreakpointDesc, width: number): BreakpointName;
718
770
  declare function useScreen(breakpointDesc?: BreakpointDesc): "base" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
719
771
 
720
- export { ArrayRender, Counter, DateRender, DefBreakpointDesc, False, If, Observer, Pipe, Scope, SizeBox, Styles, Switch, Toggle, True, When, breakpoints, childrenLoop, createExternalState, createStorageState, cx, formatDate, getCurrentBreakpoint, safePromiseTry, useControlled, useScreen };
721
- export type { ArrayRenderProps, BreakpointDesc, BreakpointName, CreateStateListener, CxInput, DateRenderProps, ElseIfProps, ElseProps, ExternalSideEffect, ExternalState, ExternalStateOptions, ExternalWithKernel, FalseProps, IfProps, ObserverProps, PipeProps, Responsive, ScopeProps, StorageStateOptions, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, Transform, TrueProps, UseControlledOptions, WhenProps };
772
+ 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, useControlled, useScreen };
773
+ 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 };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import a,{useMemo as g,Fragment as E,Children as L,isValidElement as H,cloneElement as V,useEffect as w,useState as O,useRef as k,useCallback as Z}from"react";function Y(e,n){if(e===void 0)return;let t=0;if(Array.isArray(e)){for(const r of e)if(n(r,t++)===!1)break}else n(e,t)}const z=(e,n)=>e===n,v=e=>a.createElement(a.Fragment,null,e.children);v.displayName="Switch_Case";const N=e=>a.createElement(a.Fragment,null,e.children);N.displayName="Switch_Default";const y=e=>{const{value:n,compare:t=z,children:r,strict:o=!1}=e,l=new Set;let i=null,m=null,d=!1;return Y(r,(s,c)=>{if(!a.isValidElement(s))throw new Error(`Switch Children only accepts valid React elements at index ${c}`);const u=s.type;if(u.displayName===v.displayName){const f=s.props;if(l.has(f.value))throw new Error(`Switch found duplicate Case value at index ${c}: ${JSON.stringify(f.value)}${o?" (detected in strict mode)":""}`);if(l.add(f.value),!i&&t(n,f.value)&&(i=f.children,o===!1))return!1}else if(u.displayName===N.displayName){if(d)throw new Error(`Switch can only have one Default child at index ${c}`);if(d=!0,m=s.props.children,!o&&i)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(u.displayName||u.name||u)} at index ${c}`)}),a.createElement(a.Fragment,null,i??m)};y.displayName="Switch",y.Case=v,y.Default=N,y.createTyped=function(){return{Switch:y,Case:v,Default:N}};const b=e=>a.createElement(a.Fragment,null,e.children),M=({children:e})=>a.createElement(a.Fragment,null,e),x=e=>a.createElement(a.Fragment,null,e.children);b.displayName="If_Then",M.displayName="If_Else",x.displayName="If_ElseIf";const p=({condition:e,children:n})=>{let t=null,r=null;const o=[];if(a.Children.forEach(n,l=>{if(!a.isValidElement(l))throw new Error("If component only accepts valid React elements");const i=l.type;if(i.displayName===b.displayName){if(t)throw new Error("If component can only have one Then child");t=l}else if(i.displayName===x.displayName)o.push(l);else if(i.displayName===M.displayName){if(r)throw new Error("If component can only have one Else child");r=l}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(i.displayName||i.name||i)}`)}),e)return t?a.createElement(a.Fragment,null,t.props.children):null;for(const l of o)if(l.props.condition)return a.createElement(a.Fragment,null,l.props.children);return r?a.createElement(a.Fragment,null,r.props.children):null};p.displayName="If",p.Then=b,p.ElseIf=x,p.Else=M,p.createTyped=function(){return{If:p,Then:b,ElseIf:x,Else:M}};const U=({condition:e,children:n})=>e?a.createElement(a.Fragment,null,n):null,G=({condition:e,children:n})=>e===!1?a.createElement(a.Fragment,null,n):null,q=({all:e,any:n,none:t,children:r,fallback:o})=>g(()=>(e&&(n||t)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(e&&e.length>0&&e.every(Boolean)||n&&n.length>0&&n.some(Boolean)||t&&t.length>0&&t.every(l=>!l))),[e,n,t])?a.createElement(a.Fragment,null,r):a.createElement(a.Fragment,null,o||null),K=({data:e,transform:n,render:t,fallback:r})=>{const o=g(()=>n.reduce((l,i)=>i(l),e),[e,n]);return o==null?a.createElement(a.Fragment,null,r||null):a.createElement(a.Fragment,null,t(o))},Q=e=>{const{children:n,h:t,w:r,size:o,height:l,width:i,className:m}=e;return a.createElement("div",{style:{width:o||r||i,height:o||t||l,flexShrink:0},className:m},n)},X=({let:e,props:n,children:t,fallback:r})=>{const o=g(()=>typeof e=="function"?e(n):e,[e,n]);return!t||!Object.keys(o).length?a.createElement(a.Fragment,null,r||null):a.createElement(a.Fragment,null,t(o))};function F(...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(r=>n.add(r));else if(typeof t=="object")for(const[r,o]of Object.entries(t))o&&n.add(r)}return Array.from(n).join(" ")}const ee=e=>typeof e=="object"&&!!e,C=({className:e,children:n,asWrapper:t=!1})=>{if(!n)return null;if(!e)return a.createElement(E,null,n);const r=typeof e=="string"?e:F(...Object.values(e));if(t)return a.createElement(t===!0?"div":t,{className:r},n);if(L.count(n)>1)return console.error("<Styles>: children has more than one child. Please check your code."),a.createElement(E,null,n);if(H(n)){const o=n;let l=o?.props?.className;return o?.type?.displayName===C.displayName&&ee(l)&&(l=F(...Object.values(l))),V(n,{className:F(r,l)})}return console.error("<Styles>: children is not a valid React element. Please check your code."),a.createElement(E,null,n)};C.displayName="W/Styles";const te=e=>{const{index:n=0,options:t,next:r,render:o}=e;w(()=>{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,i]=O(n),m=()=>{i(d=>t.length?r?r(d,t):(d+1)%t.length:d)};return o(t[l],m)},ne=({onIntersect:e,threshold:n=.1,root:t=null,rootMargin:r="0px",triggerOnce:o=!1,disabled:l=!1,children:i,className:m,style:d})=>{const s=k(null),c=k(null),u=k(!1);return w(()=>{if(l||!s.current)return;if(!window.IntersectionObserver){console.warn("IntersectionObserver is not supported in this browser");return}const f=s.current,h=I=>{I.forEach(D=>{o&&u.current||(e(D,c.current),o&&(u.current=!0,c.current?.unobserve(f)))})};return c.current=new IntersectionObserver(h,{root:t,rootMargin:r,threshold:n}),c.current.observe(f),()=>{c.current&&c.current.disconnect()}},[e,n,t,r,o,l]),w(()=>{o||(u.current=!1)},[o]),a.createElement("div",{ref:s,className:m,style:d},i)};function re(e){const{items:n,renderItem:t,filter:r}=e;return n?a.createElement(E,null,n.map((o,l)=>r&&!r(o)?null:t(o,l))):(console.error("ArrayRender: items is null"),null)}function le({source:e,format:n,children:t}){const r=g(()=>{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]),o=g(()=>r?n?n(r):r.toLocaleString():null,[r,n]);return!o||!t?null:a.createElement(a.Fragment,null,t(o))}const oe="onChange",ae="value";function se(e){const{defaultValue:n,onBeforeChange:t,trigger:r=oe,valuePropName:o=ae,props:l}=e,i=Object.prototype.hasOwnProperty.call(l,o),[m,d]=O(n),s=i?l[o]:m,c=g(()=>l[r],[l,r]),u=Z(f=>{const h=typeof f=="function"?f(s):f;t&&t(h,s)===!1||(i||d(h),c&&c(h))},[i,t,s,c]);return[s,u]}function ie(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):ie;function J(e,n={}){let t=typeof e=="function"?e():e;const r=[],{sideEffect:o,transform:l}=n,i=()=>{const s=t;return l?.get?l.get(s):s},m=s=>{const c=t,u=l?.get?l.get(c):c;t=l?.set?l.set(typeof s=="function"?s(u):s):typeof s=="function"?s(u):s,r.forEach(f=>f(t)),o&&j(o,t,c).catch(f=>{console.error("Error in external state side effect, Please do it within side effects:",f)})},d=()=>{const[s,c]=a.useState(t);return a.useEffect(()=>(r.push(c),()=>{const u=r.indexOf(c);u>-1&&r.splice(u,1)}),[]),[l?.get?l.get(s):s,m]};return{get:i,set:m,use:d,useGetter:()=>{const[s]=d();return s},__listeners:r}}function ce(e,n,t){const{storageType:r="local",sideEffect:o,transform:l}=t??{},i=r==="local"?localStorage:sessionStorage;let m=n;const d=i.getItem(e);if(d)try{m=JSON.parse(d)}catch(s){console.warn(`Failed to parse ${r}Storage value for key "${e}", using initial state:`,s),m=n}return J(m,{sideEffect:s=>{i.setItem(e,JSON.stringify(s)),o?.(s)},transform:l})}function ue(e,n){const t=n||new Date,r=t.getFullYear(),o=t.getMonth()+1,l=t.getDate(),i=t.getHours(),m=t.getMinutes(),d=t.getSeconds(),s=t.getMilliseconds(),c=t.getDay(),u=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],f=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],h=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],I=["January","February","March","April","May","June","July","August","September","October","November","December"],D=f[c],A=u[c],P=o-1,B=I[P],R=h[P],_={YY:r.toString().slice(2),YYYY:r.toString(),M:o.toString(),MM:o.toString().padStart(2,"0"),MMM:R,MMMM:B,D:l.toString(),DD:l.toString().padStart(2,"0"),d:c.toString(),dd:A,ddd:A,dddd:D,H:i.toString(),HH:i.toString().padStart(2,"0"),h:(i%12).toString(),hh:(i%12).toString().padStart(2,"0"),m:m.toString(),mm:m.toString().padStart(2,"0"),s:d.toString(),ss:d.toString().padStart(2,"0"),SSS:s.toString().padStart(3,"0"),Z:"+08:00",ZZ:"+0800",A:i<12?"AM":"PM",a:i<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,$=>_[$])}class de{count=0;next(){return this.count++}}const S=["base","xs","sm","md","lg","xl","2xl","3xl"],W={xs:475,sm:640,md:768,lg:1024,xl:1280,"2xl":1536,"3xl":1920},fe=[...S].reverse();function T(e,n){for(const t of fe){const r=e[t];if(r!==void 0&&!Number.isNaN(r)&&n>=r)return t}return"base"}function me(e=W){const[n,t]=O(T(e,window.innerWidth));return w(()=>{let r=[],o=[];const l=()=>{o.forEach(c=>c()),r=[],o=[];const i=T(e,window.innerWidth);t(i);const m=S.indexOf(i),d=S[m+1];if(d&&e[d]!==void 0){const c=e[d];if(Number.isNaN(c))throw new Error(`Invalid breakpoint value for ${d}: ${e[d]}`);{const u=window.matchMedia(`(min-width: ${c}px)`);r.push(u);const f=()=>l();u.addEventListener("change",f),o.push(()=>u.removeEventListener("change",f))}}const s=S[m-1];if(s&&e[s]!==void 0){const c=e[s];if(Number.isNaN(c))throw new Error(`Invalid breakpoint value for ${s}: ${e[s]}`);{const u=window.matchMedia(`(max-width: ${c-1}px)`);r.push(u);const f=()=>l();u.addEventListener("change",f),o.push(()=>u.removeEventListener("change",f))}}};return l(),()=>{o.forEach(i=>i())}},[e]),n}export{re as ArrayRender,de as Counter,le as DateRender,W as DefBreakpointDesc,G as False,p as If,ne as Observer,K as Pipe,X as Scope,Q as SizeBox,C as Styles,y as Switch,te as Toggle,U as True,q as When,S as breakpoints,Y as childrenLoop,J as createExternalState,ce as createStorageState,F as cx,ue as formatDate,T as getCurrentBreakpoint,j as safePromiseTry,se as useControlled,me as useScreen};
1
+ import u,{useMemo as S,Fragment as F,Children as Z,isValidElement as q,cloneElement as U,useEffect as b,useState as I,useRef as O,useCallback as G}from"react";function R(e,n){if(e===void 0)return;let t=0;if(Array.isArray(e)){for(const l of e)if(n(l,t++)===!1)break}else n(e,t)}const K=(e,n)=>e===n,N=e=>u.createElement(u.Fragment,null,e.children);N.displayName="Switch_Case";const D=e=>u.createElement(u.Fragment,null,e.children);D.displayName="Switch_Default";const w=e=>{const{value:n,compare:t=K,children:l,strict:r=!1}=e,o=new Set;let a=null,i=null,f=!1;return R(l,(s,c)=>{if(!u.isValidElement(s))throw new Error(`Switch Children only accepts valid React elements at index ${c}`);const d=s.type;if(d.displayName===N.displayName){const m=s.props;if(o.has(m.value))throw new Error(`Switch found duplicate Case value at index ${c}: ${JSON.stringify(m.value)}${r?" (detected in strict mode)":""}`);if(o.add(m.value),!a&&t(n,m.value)&&(a=m.children,r===!1))return!1}else if(d.displayName===D.displayName){if(f)throw new Error(`Switch can only have one Default child at index ${c}`);if(f=!0,i=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 ${c}`)}),u.createElement(u.Fragment,null,a??i)};w.displayName="Switch",w.Case=N,w.Default=D,w.createTyped=function(){return{Switch:w,Case:N,Default:D}};const A=e=>u.createElement(u.Fragment,null,e.children),x=({children:e})=>u.createElement(u.Fragment,null,e),M=e=>u.createElement(u.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,l=null;const r=[];if(u.Children.forEach(n,o=>{if(!u.isValidElement(o))throw new Error("If component only accepts valid React elements");const a=o.type;if(a.displayName===A.displayName){if(t)throw new Error("If component can only have one Then child");t=o}else if(a.displayName===M.displayName)r.push(o);else if(a.displayName===x.displayName){if(l)throw new Error("If component can only have one Else child");l=o}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(a.displayName||a.name||a)}`)}),e)return t?u.createElement(u.Fragment,null,t.props.children):null;for(const o of r)if(o.props.condition)return u.createElement(u.Fragment,null,o.props.children);return l?u.createElement(u.Fragment,null,l.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?u.createElement(u.Fragment,null,n):null,X=({condition:e,children:n})=>e===!1?u.createElement(u.Fragment,null,n):null,ee=({all:e,any:n,none:t,children:l,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(o=>!o))),[e,n,t])?u.createElement(u.Fragment,null,l):u.createElement(u.Fragment,null,r||null),te=({data:e,transform:n,render:t,fallback:l})=>{const r=S(()=>n.reduce((o,a)=>a(o),e),[e,n]);return r==null?u.createElement(u.Fragment,null,l||null):u.createElement(u.Fragment,null,t(r))},ne=e=>{const{children:n,h:t,w:l,size:r,height:o,width:a,className:i}=e;return u.createElement("div",{style:{width:r||l||a,height:r||t||o,flexShrink:0},className:i},n)},re=({let:e,props:n,children:t,fallback:l})=>{const r=S(()=>typeof e=="function"?e(n):e,[e,n]);return!t||!Object.keys(r).length?u.createElement(u.Fragment,null,l||null):u.createElement(u.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(l=>n.add(l));else if(typeof t=="object")for(const[l,r]of Object.entries(t))r&&n.add(l)}return Array.from(n).join(" ")}const le=e=>typeof e=="object"&&!!e,T=({className:e,children:n,asWrapper:t=!1})=>{if(!n)return null;if(!e)return u.createElement(F,null,n);const l=typeof e=="string"?e:C(...Object.values(e));if(t)return u.createElement(t===!0?"div":t,{className:l},n);if(Z.count(n)>1)return console.error("<Styles>: children has more than one child. Please check your code."),u.createElement(F,null,n);if(q(n)){const r=n;let o=r?.props?.className;return r?.type?.displayName===T.displayName&&le(o)&&(o=C(...Object.values(o))),U(n,{className:C(l,o)})}return console.error("<Styles>: children is not a valid React element. Please check your code."),u.createElement(F,null,n)};T.displayName="W/Styles";const oe=e=>{const{index:n=0,options:t,next:l,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[o,a]=I(n),i=()=>{a(f=>t.length?l?l(f,t):(f+1)%t.length:f)};return r(t[o],i)},ae=({onIntersect:e,threshold:n=.1,root:t=null,rootMargin:l="0px",triggerOnce:r=!1,disabled:o=!1,children:a,className:i,style:f})=>{const s=O(null),c=O(null),d=O(!1);return b(()=>{if(o||!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($,c.current),r&&(d.current=!0,c.current?.unobserve(m)))})};return c.current=new IntersectionObserver(g,{root:t,rootMargin:l,threshold:n}),c.current.observe(m),()=>{c.current&&c.current.disconnect()}},[e,n,t,l,r,o]),b(()=>{r||(d.current=!1)},[r]),u.createElement("div",{ref:s,className:i,style:f},a)};function se(e){const{items:n,renderItem:t,filter:l,renderEmpty:r,sort:o}=e;if(!n)return console.error("ArrayRender: items is null"),null;if(n.length===0)return r?r():null;if(o){let a=[...n];return l&&(a=a.filter(l)),a=a.sort(o),a.length===0?r?r():null:u.createElement(F,null,a.map((i,f)=>t(i,f)))}return u.createElement(F,null,n.map((a,i)=>l&&!l(a)?null:t(a,i)))}function ue({source:e,format:n,children:t}){const l=S(()=>{if(e instanceof Date)return e;if(typeof e=="string"||typeof e=="number"){const o=new Date(e);return isNaN(o.getTime())?null:o}return null},[e]),r=S(()=>l?n?n(l):l.toLocaleString():null,[l,n]);return!r||!t?null:u.createElement(u.Fragment,null,t(r))}const ie="onChange",ce="value";function fe(e){const{defaultValue:n,onBeforeChange:t,trigger:l=ie,valuePropName:r=ce,props:o}=e,a=Object.prototype.hasOwnProperty.call(o,r),[i,f]=I(n),s=a?o[r]:i,c=S(()=>o[l],[o,l]),d=G(m=>{const g=typeof m=="function"?m(s):m;t&&t(g,s)===!1||(a||f(g),c&&c(g))},[a,t,s,c]);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;function W(e,n={}){let t=typeof e=="function"?e():e;const l=[],{sideEffect:r,transform:o}=n,a=()=>{const s=t;return o?.get?o.get(s):s},i=s=>{const c=t,d=o?.get?o.get(c):c;t=o?.set?o.set(typeof s=="function"?s(d):s):typeof s=="function"?s(d):s,l.forEach(m=>m(t)),r&&J(r,t,c).catch(m=>{console.error("Error in external state side effect, Please do it within side effects:",m)})},f=()=>{const[s,c]=u.useState(t);return u.useEffect(()=>(l.push(c),()=>{const d=l.indexOf(c);d>-1&&l.splice(d,1)}),[]),[o?.get?o.get(s):s,i]};return{get:a,set:i,use:f,useGetter:()=>{const[s]=f();return s},__listeners:l}}function me(e,n,t){const{storageType:l="local",sideEffect:r,transform:o}=t??{},a=l==="local"?localStorage:sessionStorage;let i=n;const f=a.getItem(e);if(f)try{i=JSON.parse(f)}catch(s){console.warn(`Failed to parse ${l}Storage value for key "${e}", using initial state:`,s),i=n}return W(i,{sideEffect:s=>{a.setItem(e,JSON.stringify(s)),r?.(s)},transform:o})}function he(e,n){const t=n||new Date,l=t.getFullYear(),r=t.getMonth()+1,o=t.getDate(),a=t.getHours(),i=t.getMinutes(),f=t.getSeconds(),s=t.getMilliseconds(),c=t.getDay(),d=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],m=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],g=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],y=["January","February","March","April","May","June","July","August","September","October","November","December"],$=m[c],Y=d[c],j=r-1,_=y[j],H=g[j],z={YY:l.toString().slice(2),YYYY:l.toString(),M:r.toString(),MM:r.toString().padStart(2,"0"),MMM:H,MMMM:_,D:o.toString(),DD:o.toString().padStart(2,"0"),d:c.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:i.toString(),mm:i.toString().padStart(2,"0"),s:f.toString(),ss:f.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 pe{count=0;next(){return this.count++}}const v=["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 ge(e){return e==null?!1:typeof e=="string"?e.trim().length>0:Array.isArray(e)?e.length>0:!0}function ye(e){return e!=null}function p(e,n){return`${String(e)} ${n}`}const k={email:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,url:/^(https?:\/\/)?([\w.-]+)\.([a-z]{2,6})([/\w .-]*)*\/?$/,phone:/^1[3-9]\d{9}$/};function B(e,n,t,l,r){const o=ge(n),a=ye(n);if(t.required&&!o){h(r,e,t.message??p(e,"\u4E3A\u5FC5\u586B\u9879"));return}if(!(!a&&!t.required)){if(t.dependsOn){const i=t.dependsOn(l);i===!1?h(r,e,t.message??p(e,"\u4F9D\u8D56\u6761\u4EF6\u672A\u6EE1\u8DB3")):typeof i=="string"&&h(r,e,i)}if(typeof n=="string"){const i=t,{len:f,min:s,max:c,regex:d,email:m,url:g,phone:y}=i;typeof f=="number"&&n.length!==f&&h(r,e,t.message??p(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${f}`)),typeof s=="number"&&n.length<s&&h(r,e,t.message??p(e,`\u957F\u5EA6\u4E0D\u80FD\u5C11\u4E8E ${s}`)),typeof c=="number"&&n.length>c&&h(r,e,t.message??p(e,`\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${c}`)),d&&!d.test(n)&&h(r,e,t.message??p(e,"\u683C\u5F0F\u4E0D\u6B63\u786E")),m&&!k.email.test(n)&&h(r,e,t.message??p(e,"\u4E0D\u662F\u6709\u6548\u7684\u90AE\u7BB1")),g&&!k.url.test(n)&&h(r,e,t.message??p(e,"\u4E0D\u662F\u6709\u6548\u7684URL")),y&&!k.phone.test(n)&&h(r,e,t.message??p(e,"\u4E0D\u662F\u6709\u6548\u7684\u624B\u673A\u53F7"))}if(typeof n=="number"){const i=t,{min:f,max:s}=i;typeof f=="number"&&n<f&&h(r,e,t.message??p(e,`\u4E0D\u80FD\u5C0F\u4E8E ${f}`)),typeof s=="number"&&n>s&&h(r,e,t.message??p(e,`\u4E0D\u80FD\u5927\u4E8E ${s}`))}if(Array.isArray(n)){const i=t,{len:f,min:s,max:c,unique:d,elementRule:m}=i;typeof f=="number"&&n.length!==f&&h(r,e,t.message??p(e,`\u957F\u5EA6\u5FC5\u987B\u4E3A ${f}`)),typeof s=="number"&&n.length<s&&h(r,e,t.message??p(e,`\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E ${s}`)),typeof c=="number"&&n.length>c&&h(r,e,t.message??p(e,`\u957F\u5EA6\u4E0D\u80FD\u5927\u4E8E ${c}`)),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)=>{B(`${String(e)}[${y}]`,g,m,l,r)})}if(t.validator){const i=t.validator?.(n,l);i===!1?h(r,e,t.message??p(e,"\u6821\u9A8C\u672A\u901A\u8FC7")):typeof i=="string"&&h(r,e,i)}}}function Ee(e,n){const t={};for(const r in n){const o=r,a=n[o];if(!a)continue;const i=e[o];if(Array.isArray(a))for(const f of a)B(o,i,f,e,t);else B(o,i,a,e,t)}const l=Object.values(t).reduce((r,o)=>(o&&r.push(...o),r),[]);return l.length>0?{valid:!1,errors:l,fieldErrors:t}:{valid:!0,data:e}}const Se=[...v].reverse();function P(e,n){for(const t of Se){const l=e[t];if(l!==void 0&&!Number.isNaN(l)&&n>=l)return t}return"base"}function we(e=L){const[n,t]=I(P(e,window.innerWidth));return b(()=>{let l=[],r=[];const o=()=>{r.forEach(c=>c()),l=[],r=[];const a=P(e,window.innerWidth);t(a);const i=v.indexOf(a),f=v[i+1];if(f&&e[f]!==void 0){const c=e[f];if(Number.isNaN(c))throw new Error(`Invalid breakpoint value for ${f}: ${e[f]}`);{const d=window.matchMedia(`(min-width: ${c}px)`);l.push(d);const m=()=>o();d.addEventListener("change",m),r.push(()=>d.removeEventListener("change",m))}}const s=v[i-1];if(s&&e[s]!==void 0){const c=e[s];if(Number.isNaN(c))throw new Error(`Invalid breakpoint value for ${s}: ${e[s]}`);{const d=window.matchMedia(`(max-width: ${c-1}px)`);l.push(d);const m=()=>o();d.addEventListener("change",m),r.push(()=>d.removeEventListener("change",m))}}};return o(),()=>{r.forEach(a=>a())}},[e]),n}export{se as ArrayRender,pe as Counter,ue as DateRender,L as DefBreakpointDesc,X as False,E as If,ae as Observer,te as Pipe,re as Scope,ne as SizeBox,T as Styles,w as Switch,oe as Toggle,Q as True,ee as When,v as breakpoints,R as childrenLoop,W as createExternalState,me as createStorageState,C as cx,he as formatDate,P as getCurrentBreakpoint,Ee as ruleChecker,J as safePromiseTry,fe as useControlled,we as useScreen};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wwog/react",
3
- "version": "1.3.6",
3
+ "version": "1.3.9",
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",
@@ -0,0 +1,80 @@
1
+ /*
2
+ 蔡勒公式(德语:Zellers Kongruenz),是一种计算任何一日属一星期中哪一日的算法,由十九世纪德国数学家克里斯提安·蔡勒推算出来。
3
+
4
+ 对于公历,蔡勒公式为
5
+ h = (q + [13(m + 1)/5] + K + [K/4] + [J/4] - 2J) mod 7
6
+ 对于儒略历,蔡勒公式为
7
+ h = (q + [13(m + 1)/5] + K + [K/4] + 5 - J) mod 7
8
+
9
+ h 表示星期几(0 = 星期六,1 = 星期日,2 = 星期一,...,6 = 星期五)
10
+ q 代表月份中的日期
11
+ m 代表月份(3=三月,4=四月,5=五月,...,14=二月)
12
+ K 代表世纪中的年份 ((adjYear) mod 100)
13
+ J 是从零开始的世纪数(实际上是 ⌊(adjYear)/100⌋)
14
+ [...] 表示取整函数
15
+ 注意:在此算法中,一月和二月被视为上一年的第 13 和第 14 个月。
16
+ 对于ISO 周期中的星期几 d (1 ..= 7)
17
+ d = ((h + 5) mod 7) + 1
18
+
19
+ 上面公式依赖于数学家对模除法的定义,即−2 mod 7 等于正 5。
20
+ 遗憾的是,在大多数计算机语言实现取余函数时采用截断方式,−2 mod 7 会返回−2 的结果。
21
+ 因此,要在计算机上实现泽勒同余,应略微调整公式以确保分子为正。
22
+ 最简单的办法是将−2J 替换为+5J,将−J 替换为+6J。
23
+
24
+ 对于公历,蔡勒公式变体为:
25
+ h = (q + [13(m + 1)/5] + K + [K / 4] + [J / 4] + 5J) mod 7
26
+ 对于儒略历,蔡勒公式变体为:
27
+ h = (q + [13(m + 1)/5] + K + [K / 4] + 5 + 6J) mod 7
28
+
29
+ 在使用计算机时,年份处理为4位数更简单,因此RFC 3339附录B中提及用于公历的情况
30
+ 对于公历, 变体为:
31
+ h + (q + [ 13(m +1) /5] + Y + [Y /4] -[Y/100] + [Y/400] ) mod 7
32
+ 对于儒略历, 变体为:
33
+ h = (q + [13(m + 1)/5] + Y + [Y / 4] + 5) mod 7
34
+
35
+ */
36
+
37
+ /**
38
+ * 计算给定日期的星期几(基于 Michael Keith & Tom Craver 的优化算法)仅公历
39
+ * @param y - 年份(4位数,如 2023)
40
+ * @param m - 月份(1-12)
41
+ * @param d - 日期(1-31)
42
+ * @returns 星期几(0=周日, 1=周一, ..., 6=周六)
43
+ */
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;
47
+ return (
48
+ (Math.floor((23 * m) / 9) +
49
+ adjustedD +
50
+ 4 +
51
+ Math.floor(y / 4) -
52
+ Math.floor(y / 100) +
53
+ Math.floor(y / 400)) %
54
+ 7
55
+ );
56
+ }
57
+
58
+ /**
59
+ * 计算儒略历日期的星期几
60
+ * @param y - 年份(4位数,如 1582)
61
+ * @param m - 月份(1-12)
62
+ * @param d - 日期(1-31)
63
+ * @returns 星期几(0=周六, 1=周日, ..., 6=周五)
64
+ */
65
+ export function weekdayJulian(y: number, m: number, d: number): number {
66
+ const adjustedY = m < 3 ? y - 1 : y;
67
+ const adjustedD = d + adjustedY;
68
+ if (m < 3) {
69
+ m += 12;
70
+ y--;
71
+ }
72
+ return (
73
+ (adjustedD +
74
+ Math.floor((13 * (m + 1)) / 5) +
75
+ adjustedY +
76
+ Math.floor(adjustedY / 4) +
77
+ 5) %
78
+ 7
79
+ );
80
+ }
@@ -0,0 +1,5 @@
1
+ import * as zllersKongruenz from "./date/zellersKongruenz";
2
+
3
+ export const weekday = {
4
+ zllersKongruenz,
5
+ };
@@ -5,18 +5,48 @@ export interface ArrayRenderProps<T> {
5
5
  items: T[];
6
6
  renderItem: (item: T, index: number) => React.ReactNode;
7
7
  filter?: (item: T) => boolean;
8
+ renderEmpty?: () => React.ReactNode;
9
+ sort?: (a: T, b: T) => number;
8
10
  }
9
11
  //#endregion component Types
10
12
 
11
13
  //#region component
12
14
  export function ArrayRender<T>(props: ArrayRenderProps<T>): ReactNode {
13
- const { items, renderItem, filter } = props;
15
+ const { items, renderItem, filter, renderEmpty, sort } = props;
14
16
 
15
17
  if (!items) {
16
18
  console.error("ArrayRender: items is null");
17
19
  return null;
18
20
  }
19
21
 
22
+ if (items.length === 0) {
23
+ return renderEmpty ? renderEmpty() : null;
24
+ }
25
+
26
+ // 如果需要排序,先处理排序和过滤
27
+ if (sort) {
28
+ let processedItems = [...items];
29
+
30
+ if (filter) {
31
+ processedItems = processedItems.filter(filter);
32
+ }
33
+
34
+ processedItems = processedItems.sort(sort);
35
+
36
+ if (processedItems.length === 0) {
37
+ return renderEmpty ? renderEmpty() : null;
38
+ }
39
+
40
+ return (
41
+ <Fragment>
42
+ {processedItems.map((item, index) => {
43
+ return renderItem(item, index);
44
+ })}
45
+ </Fragment>
46
+ );
47
+ }
48
+
49
+ // 如果不需要排序,保持原来的循环中过滤方式
20
50
  return (
21
51
  <Fragment>
22
52
  {items.map((item, index) => {
@@ -3,4 +3,5 @@ export * from "./cx";
3
3
  export * from "./reactUtils";
4
4
  export * from "./sundry";
5
5
  export * from "./promise";
6
- export * from "./constants";
6
+ export * from "./constants";
7
+ export * from "./ruleChecker";