@wwog/react 1.3.1 → 1.3.3

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
@@ -463,6 +463,10 @@ You can also use a container wrapper element:
463
463
 
464
464
  > 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.
465
465
 
466
+ ### `createStorageState` (v1.3.2+)
467
+
468
+ > Extends from createExternalState and uses storage to persist state, supports `localStorage` and `sessionStorage`
469
+
466
470
  ```tsx
467
471
  import { createExternalState } from "@wwog/react";
468
472
 
package/dist/index.d.mts CHANGED
@@ -654,6 +654,12 @@ interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
654
654
  * ```
655
655
  */
656
656
  declare function createExternalState<T, U = T>(initialState: T | (() => T), options?: ExternalStateOptions<T, U>): ExternalState<T, U>;
657
+ interface StorageStateOptions<T, U> {
658
+ sideEffect?: (newState: T) => void;
659
+ transform?: Transform<T, U>;
660
+ storageType: "local" | "session";
661
+ }
662
+ declare function createStorageState<T, U = T>(key: string, initialState: T, options?: StorageStateOptions<T, U>): ExternalState<T, U>;
657
663
 
658
664
  /**
659
665
  * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
@@ -702,5 +708,5 @@ declare class Counter {
702
708
 
703
709
  declare const safePromiseTry: <T, U extends unknown[]>(callbackFn: (...args: U) => T | PromiseLike<T>, ...args: U) => Promise<Awaited<T>>;
704
710
 
705
- export { ArrayRender, Counter, DateRender, False, If, Observer, Pipe, Scope, SizeBox, Styles, Switch, Toggle, True, When, childrenLoop, createExternalState, cx, formatDate, safePromiseTry, useControlled };
706
- export type { ArrayRenderProps, CreateStateListener, CxInput, DateRenderProps, ElseIfProps, ElseProps, ExternalSideEffect, ExternalState, ExternalStateOptions, ExternalWithKernel, FalseProps, IfProps, ObserverProps, PipeProps, ScopeProps, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, Transform, TrueProps, UseControlledOptions, WhenProps };
711
+ export { ArrayRender, Counter, DateRender, False, If, Observer, Pipe, Scope, SizeBox, Styles, Switch, Toggle, True, When, childrenLoop, createExternalState, createStorageState, cx, formatDate, safePromiseTry, useControlled };
712
+ export type { ArrayRenderProps, CreateStateListener, CxInput, DateRenderProps, ElseIfProps, ElseProps, ExternalSideEffect, ExternalState, ExternalStateOptions, ExternalWithKernel, FalseProps, IfProps, ObserverProps, PipeProps, ScopeProps, StorageStateOptions, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, Transform, TrueProps, UseControlledOptions, WhenProps };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import a,{useMemo as y,Children as _,Fragment as E,isValidElement as W,cloneElement as H,useEffect as I,useState as A,useRef as x,useCallback as B}from"react";function P(t,n){if(t===void 0)return;let e=0;if(Array.isArray(t)){for(const r of t)if(n(r,e++)===!1)break}else n(t,e)}const V=(t,n)=>t===n,w=t=>a.createElement(a.Fragment,null,t.children);w.displayName="Switch_Case";const N=t=>a.createElement(a.Fragment,null,t.children);N.displayName="Switch_Default";const g=t=>{const{value:n,compare:e=V,children:r,strict:o=!1}=t,l=new Set;let s=null,f=null,m=!1;return P(r,(c,i)=>{if(!a.isValidElement(c))throw new Error(`Switch Children only accepts valid React elements at index ${i}`);const u=c.type;if(u.displayName===w.displayName){const d=c.props;if(l.has(d.value))throw new Error(`Switch found duplicate Case value at index ${i}: ${JSON.stringify(d.value)}${o?" (detected in strict mode)":""}`);if(l.add(d.value),!s&&e(n,d.value)&&(s=d.children,o===!1))return!1}else if(u.displayName===N.displayName){if(m)throw new Error(`Switch can only have one Default child at index ${i}`);if(m=!0,f=c.props.children,!o&&s)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(u.displayName||u.name||u)} at index ${i}`)}),a.createElement(a.Fragment,null,s??f)};g.displayName="Switch",g.Case=w,g.Default=N,g.createTyped=function(){return{Switch:g,Case:w,Default:N}};const v=t=>a.createElement(a.Fragment,null,t.children),b=({children:t})=>a.createElement(a.Fragment,null,t),M=t=>a.createElement(a.Fragment,null,t.children);v.displayName="If_Then",b.displayName="If_Else",M.displayName="If_ElseIf";const p=({condition:t,children:n})=>{let e=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 s=l.type;if(s.displayName===v.displayName){if(e)throw new Error("If component can only have one Then child");e=l}else if(s.displayName===M.displayName)o.push(l);else if(s.displayName===b.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(s.displayName||s.name||s)}`)}),t)return e?a.createElement(a.Fragment,null,e.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=v,p.ElseIf=M,p.Else=b,p.createTyped=function(){return{If:p,Then:v,ElseIf:M,Else:b}};const Z=({condition:t,children:n})=>t?a.createElement(a.Fragment,null,n):null,z=({condition:t,children:n})=>t===!1?a.createElement(a.Fragment,null,n):null,L=({all:t,any:n,none:e,children:r,fallback:o})=>y(()=>(t&&(n||e)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(t&&t.length>0&&t.every(Boolean)||n&&n.length>0&&n.some(Boolean)||e&&e.length>0&&e.every(l=>!l))),[t,n,e])?a.createElement(a.Fragment,null,r):a.createElement(a.Fragment,null,o||null),G=({data:t,transform:n,render:e,fallback:r})=>{const o=y(()=>n.reduce((l,s)=>s(l),t),[t,n]);return o==null?a.createElement(a.Fragment,null,r||null):a.createElement(a.Fragment,null,e(o))},U=t=>{const{children:n,h:e,w:r,size:o,height:l,width:s,className:f}=t;return a.createElement("div",{style:{width:o||r||s,height:o||e||l,flexShrink:0},className:f},n)},q=({let:t,props:n,children:e,fallback:r})=>{const o=y(()=>typeof t=="function"?t(n):t,[t,n]);return!e||!Object.keys(o).length?a.createElement(a.Fragment,null,r||null):a.createElement(a.Fragment,null,e(o))};function F(...t){const n=new Set;for(const e of t)if(e){if(typeof e=="string")n.add(e);else if(Array.isArray(e))e.forEach(r=>n.add(r));else if(typeof e=="object")for(const[r,o]of Object.entries(e))o&&n.add(r)}return Array.from(n).join(" ")}const K=t=>typeof t=="object"&&!!t,T=({className:t,children:n,asWrapper:e=!1})=>{if(!n)return null;if(_.count(n)>1)return console.error("<Styles>: children has more than one child. Please check your code."),a.createElement(E,null,n);if(!t)return a.createElement(E,null,n);const r=typeof t=="string"?t:F(...Object.values(t));if(e)return a.createElement(e===!0?"div":e,{className:r},n);if(W(n)){const o=n;let l=o?.props?.className;return o?.type?.displayName===T.displayName&&K(l)&&(l=F(...Object.values(l))),H(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)};T.displayName="W/Styles";const Q=t=>{const{index:n=0,options:e,next:r,render:o}=t;I(()=>{if(e.length<n+1)throw new Error(`Index ${n} is out of bounds for options array of length ${e.length}. Defaulting to first option.`)},[n,e]);const[l,s]=A(n),f=()=>{s(m=>e.length?r?r(m,e):(m+1)%e.length:m)};return o(e[l],f)},X=({onIntersect:t,threshold:n=.1,root:e=null,rootMargin:r="0px",triggerOnce:o=!1,disabled:l=!1,children:s,className:f,style:m})=>{const c=x(null),i=x(null),u=x(!1);return I(()=>{if(l||!c.current)return;if(!window.IntersectionObserver){console.warn("IntersectionObserver is not supported in this browser");return}const d=c.current,h=D=>{D.forEach(S=>{if(S.isIntersecting){if(o&&u.current)return;t(S,i.current),o&&(u.current=!0,i.current?.unobserve(d))}})};return i.current=new IntersectionObserver(h,{root:e,rootMargin:r,threshold:n}),i.current.observe(d),()=>{i.current&&i.current.disconnect()}},[t,n,e,r,o,l]),I(()=>{o||(u.current=!1)},[o]),a.createElement("div",{ref:c,className:f,style:m},s)};function $(t){const{items:n,renderItem:e,filter:r}=t;return n?a.createElement(E,null,n.map((o,l)=>r&&!r(o)?null:e(o,l))):(console.error("ArrayRender: items is null"),null)}function ee({source:t,format:n,children:e}){const r=y(()=>{if(t instanceof Date)return t;if(typeof t=="string"||typeof t=="number"){const l=new Date(t);return isNaN(l.getTime())?null:l}return null},[t]),o=y(()=>r?n?n(r):r.toLocaleString():null,[r,n]);return!o||!e?null:a.createElement(a.Fragment,null,e(o))}const te="onChange",ne="value";function re(t){const{defaultValue:n,onBeforeChange:e,trigger:r=te,valuePropName:o=ne,props:l}=t,s=Object.prototype.hasOwnProperty.call(l,o),[f,m]=A(n),c=s?l[o]:f,i=y(()=>l[r],[l,r]),u=B(d=>{const h=typeof d=="function"?d(c):d;e&&e(h,c)===!1||(s||m(h),i&&i(h))},[s,e,c,i]);return[c,u]}function le(t,...n){try{const e=t(...n);return e instanceof Promise?e:Promise.resolve(e)}catch(e){return Promise.reject(e)}}const Y=typeof Promise.try=="function"?Promise.try.bind(Promise):le;function ae(t,n={}){let e=typeof t=="function"?t():t;const r=[],{sideEffect:o,transform:l}=n,s=()=>{const c=e;return l?.get?l.get(c):c},f=c=>{const i=e,u=l?.get?l.get(i):i;e=l?.set?l.set(typeof c=="function"?c(u):c):typeof c=="function"?c(u):c,r.forEach(d=>d(e)),o&&Y(o,e,i).catch(d=>{console.error("Error in external state side effect, Please do it within side effects:",d)})},m=()=>{const[c,i]=a.useState(e);return a.useEffect(()=>(r.push(i),()=>{const u=r.indexOf(i);u>-1&&r.splice(u,1)}),[]),[l?.get?l.get(c):c,f]};return{get:s,set:f,use:m,useGetter:()=>{const[c]=m();return c},__listeners:r}}function oe(t,n){const e=n||new Date,r=e.getFullYear(),o=e.getMonth()+1,l=e.getDate(),s=e.getHours(),f=e.getMinutes(),m=e.getSeconds(),c=e.getMilliseconds(),i=e.getDay(),u=["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"],D=["January","February","March","April","May","June","July","August","September","October","November","December"],S=d[i],C=u[i],O=o-1,k=D[O],j=h[O],J={YY:r.toString().slice(2),YYYY:r.toString(),M:o.toString(),MM:o.toString().padStart(2,"0"),MMM:j,MMMM:k,D:l.toString(),DD:l.toString().padStart(2,"0"),d:i.toString(),dd:C,ddd:C,dddd:S,H:s.toString(),HH:s.toString().padStart(2,"0"),h:(s%12).toString(),hh:(s%12).toString().padStart(2,"0"),m:f.toString(),mm:f.toString().padStart(2,"0"),s:m.toString(),ss:m.toString().padStart(2,"0"),SSS:c.toString().padStart(3,"0"),Z:"+08:00",ZZ:"+0800",A:s<12?"AM":"PM",a:s<12?"am":"pm"};return t.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,R=>J[R])}class se{count=0;next(){return this.count++}}export{$ as ArrayRender,se as Counter,ee as DateRender,z as False,p as If,X as Observer,G as Pipe,q as Scope,U as SizeBox,T as Styles,g as Switch,Q as Toggle,Z as True,L as When,P as childrenLoop,ae as createExternalState,F as cx,oe as formatDate,Y as safePromiseTry,re as useControlled};
1
+ import a,{useMemo as g,Fragment as E,Children as W,isValidElement as H,cloneElement as B,useEffect as D,useState as A,useRef as O,useCallback as V}from"react";function P(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,w=e=>a.createElement(a.Fragment,null,e.children);w.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 s=null,u=null,f=!1;return P(r,(c,i)=>{if(!a.isValidElement(c))throw new Error(`Switch Children only accepts valid React elements at index ${i}`);const d=c.type;if(d.displayName===w.displayName){const m=c.props;if(l.has(m.value))throw new Error(`Switch found duplicate Case value at index ${i}: ${JSON.stringify(m.value)}${o?" (detected in strict mode)":""}`);if(l.add(m.value),!s&&t(n,m.value)&&(s=m.children,o===!1))return!1}else if(d.displayName===N.displayName){if(f)throw new Error(`Switch can only have one Default child at index ${i}`);if(f=!0,u=c.props.children,!o&&s)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(d.displayName||d.name||d)} at index ${i}`)}),a.createElement(a.Fragment,null,s??u)};y.displayName="Switch",y.Case=w,y.Default=N,y.createTyped=function(){return{Switch:y,Case:w,Default:N}};const v=e=>a.createElement(a.Fragment,null,e.children),b=({children:e})=>a.createElement(a.Fragment,null,e),M=e=>a.createElement(a.Fragment,null,e.children);v.displayName="If_Then",b.displayName="If_Else",M.displayName="If_ElseIf";const h=({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 s=l.type;if(s.displayName===v.displayName){if(t)throw new Error("If component can only have one Then child");t=l}else if(s.displayName===M.displayName)o.push(l);else if(s.displayName===b.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(s.displayName||s.name||s)}`)}),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};h.displayName="If",h.Then=v,h.ElseIf=M,h.Else=b,h.createTyped=function(){return{If:h,Then:v,ElseIf:M,Else:b}};const z=({condition:e,children:n})=>e?a.createElement(a.Fragment,null,n):null,L=({condition:e,children:n})=>e===!1?a.createElement(a.Fragment,null,n):null,U=({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),G=({data:e,transform:n,render:t,fallback:r})=>{const o=g(()=>n.reduce((l,s)=>s(l),e),[e,n]);return o==null?a.createElement(a.Fragment,null,r||null):a.createElement(a.Fragment,null,t(o))},$=e=>{const{children:n,h:t,w:r,size:o,height:l,width:s,className:u}=e;return a.createElement("div",{style:{width:o||r||s,height:o||t||l,flexShrink:0},className:u},n)},q=({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 K=e=>typeof e=="object"&&!!e,x=({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(W.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===x.displayName&&K(l)&&(l=F(...Object.values(l))),B(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)};x.displayName="W/Styles";const Q=e=>{const{index:n=0,options:t,next:r,render:o}=e;D(()=>{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),u=()=>{s(f=>t.length?r?r(f,t):(f+1)%t.length:f)};return o(t[l],u)},X=({onIntersect:e,threshold:n=.1,root:t=null,rootMargin:r="0px",triggerOnce:o=!1,disabled:l=!1,children:s,className:u,style:f})=>{const c=O(null),i=O(null),d=O(!1);return D(()=>{if(l||!c.current)return;if(!window.IntersectionObserver){console.warn("IntersectionObserver is not supported in this browser");return}const m=c.current,p=I=>{I.forEach(S=>{if(S.isIntersecting){if(o&&d.current)return;e(S,i.current),o&&(d.current=!0,i.current?.unobserve(m))}})};return i.current=new IntersectionObserver(p,{root:t,rootMargin:r,threshold:n}),i.current.observe(m),()=>{i.current&&i.current.disconnect()}},[e,n,t,r,o,l]),D(()=>{o||(d.current=!1)},[o]),a.createElement("div",{ref:c,className:u,style:f},s)};function ee(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 te({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 ne="onChange",re="value";function le(e){const{defaultValue:n,onBeforeChange:t,trigger:r=ne,valuePropName:o=re,props:l}=e,s=Object.prototype.hasOwnProperty.call(l,o),[u,f]=A(n),c=s?l[o]:u,i=g(()=>l[r],[l,r]),d=V(m=>{const p=typeof m=="function"?m(c):m;t&&t(p,c)===!1||(s||f(p),i&&i(p))},[s,t,c,i]);return[c,d]}function oe(e,...n){try{const t=e(...n);return t instanceof Promise?t:Promise.resolve(t)}catch(t){return Promise.reject(t)}}const Y=typeof Promise.try=="function"?Promise.try.bind(Promise):oe;function k(e,n={}){let t=typeof e=="function"?e():e;const r=[],{sideEffect:o,transform:l}=n,s=()=>{const c=t;return l?.get?l.get(c):c},u=c=>{const i=t,d=l?.get?l.get(i):i;t=l?.set?l.set(typeof c=="function"?c(d):c):typeof c=="function"?c(d):c,r.forEach(m=>m(t)),o&&Y(o,t,i).catch(m=>{console.error("Error in external state side effect, Please do it within side effects:",m)})},f=()=>{const[c,i]=a.useState(t);return a.useEffect(()=>(r.push(i),()=>{const d=r.indexOf(i);d>-1&&r.splice(d,1)}),[]),[l?.get?l.get(c):c,u]};return{get:s,set:u,use:f,useGetter:()=>{const[c]=f();return c},__listeners:r}}function ae(e,n,t){const{storageType:r="local",sideEffect:o,transform:l}=t??{},s=r==="local"?localStorage:sessionStorage;let u=n;const f=s.getItem(e);if(f)try{u=JSON.parse(f)}catch(c){console.warn(`Failed to parse ${r}Storage value for key "${e}", using initial state:`,c),u=n}return k(u,{sideEffect:c=>{s.setItem(e,JSON.stringify(c)),o?.(c)},transform:l})}function se(e,n){const t=n||new Date,r=t.getFullYear(),o=t.getMonth()+1,l=t.getDate(),s=t.getHours(),u=t.getMinutes(),f=t.getSeconds(),c=t.getMilliseconds(),i=t.getDay(),d=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],m=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],p=["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"],S=m[i],T=d[i],C=o-1,j=I[C],J=p[C],R={YY:r.toString().slice(2),YYYY:r.toString(),M:o.toString(),MM:o.toString().padStart(2,"0"),MMM:J,MMMM:j,D:l.toString(),DD:l.toString().padStart(2,"0"),d:i.toString(),dd:T,ddd:T,dddd:S,H:s.toString(),HH:s.toString().padStart(2,"0"),h:(s%12).toString(),hh:(s%12).toString().padStart(2,"0"),m:u.toString(),mm:u.toString().padStart(2,"0"),s:f.toString(),ss:f.toString().padStart(2,"0"),SSS:c.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,_=>R[_])}class ce{count=0;next(){return this.count++}}export{ee as ArrayRender,ce as Counter,te as DateRender,L as False,h as If,X as Observer,G as Pipe,q as Scope,$ as SizeBox,x as Styles,y as Switch,Q as Toggle,z as True,U as When,P as childrenLoop,k as createExternalState,ae as createStorageState,F as cx,se as formatDate,Y as safePromiseTry,le as useControlled};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wwog/react",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
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",
@@ -91,12 +91,6 @@ export const Styles: FC<StylesProps> = ({
91
91
  if (!children) {
92
92
  return null;
93
93
  }
94
- if (Children.count(children) > 1) {
95
- console.error(
96
- "<Styles>: children has more than one child. Please check your code."
97
- );
98
- return <Fragment>{children}</Fragment>;
99
- }
100
94
 
101
95
  if (!className) {
102
96
  return <Fragment>{children}</Fragment>;
@@ -109,6 +103,12 @@ export const Styles: FC<StylesProps> = ({
109
103
  const Tag = asWrapper === true ? "div" : asWrapper;
110
104
  return <Tag className={generatedClassName}>{children}</Tag>;
111
105
  }
106
+ if (Children.count(children) > 1) {
107
+ console.error(
108
+ "<Styles>: children has more than one child. Please check your code."
109
+ );
110
+ return <Fragment>{children}</Fragment>;
111
+ }
112
112
  if (isValidElement(children)) {
113
113
  const typeChildren = children as any;
114
114
  let processedChildClassName = typeChildren?.props?.className;
@@ -1,10 +1,10 @@
1
- import { expect, describe, it, vi } from "vitest";
1
+ import { expect, describe, it, vi, beforeEach } from "vitest";
2
2
  import { render } from "vitest-browser-react";
3
3
  import {
4
4
  createExternalState,
5
+ createStorageState,
5
6
  type ExternalWithKernel,
6
7
  } from "./createExternalState";
7
- import { safePromiseTry } from "./promise";
8
8
  import React from "react";
9
9
 
10
10
  describe("createExternalState", () => {
@@ -219,3 +219,172 @@ describe("createExternalState", () => {
219
219
  expect(state.get()).toBe("TEST");
220
220
  });
221
221
  });
222
+
223
+ describe("createStorageState", () => {
224
+ beforeEach(() => {
225
+ // 清理localStorage和sessionStorage
226
+ localStorage.clear();
227
+ sessionStorage.clear();
228
+ vi.clearAllMocks();
229
+ });
230
+
231
+ it("测试localStorage初始状态", () => {
232
+ const state = createStorageState("test-key", "initial", {
233
+ storageType: "local",
234
+ });
235
+ expect(state.get()).toBe("initial");
236
+ expect(localStorage.getItem("test-key")).toBeNull();
237
+ });
238
+
239
+ it("测试localStorage状态持久化", () => {
240
+ const state = createStorageState("test-key", "initial", {
241
+ storageType: "local",
242
+ });
243
+
244
+ state.set("updated");
245
+ expect(state.get()).toBe("updated");
246
+ expect(localStorage.getItem("test-key")).toBe('"updated"');
247
+ });
248
+
249
+ it("测试从localStorage恢复状态", () => {
250
+ // 预先设置localStorage值
251
+ localStorage.setItem("test-key", '"stored-value"');
252
+
253
+ const state = createStorageState("test-key", "initial", {
254
+ storageType: "local",
255
+ });
256
+
257
+ expect(state.get()).toBe("stored-value");
258
+ });
259
+
260
+ it("测试sessionStorage状态持久化", () => {
261
+ const state = createStorageState("test-key", "initial", {
262
+ storageType: "session",
263
+ });
264
+
265
+ state.set("session-updated");
266
+ expect(state.get()).toBe("session-updated");
267
+ expect(sessionStorage.getItem("test-key")).toBe('"session-updated"');
268
+ });
269
+
270
+ it("测试从sessionStorage恢复状态", () => {
271
+ // 预先设置sessionStorage值
272
+ sessionStorage.setItem("test-key", '"session-stored"');
273
+
274
+ const state = createStorageState("test-key", "initial", {
275
+ storageType: "session",
276
+ });
277
+
278
+ expect(state.get()).toBe("session-stored");
279
+ });
280
+
281
+ it("测试复杂对象的存储和恢复", () => {
282
+ interface User {
283
+ name: string;
284
+ age: number;
285
+ }
286
+
287
+ const initialUser: User = { name: "张三", age: 25 };
288
+ const state = createStorageState<User>("user-key", initialUser, {
289
+ storageType: "local",
290
+ });
291
+
292
+ const updatedUser: User = { name: "李四", age: 30 };
293
+ state.set(updatedUser);
294
+
295
+ expect(state.get()).toEqual(updatedUser);
296
+ expect(JSON.parse(localStorage.getItem("user-key")!)).toEqual(updatedUser);
297
+
298
+ // 创建新实例验证恢复
299
+ const newState = createStorageState<User>("user-key", initialUser, {
300
+ storageType: "local",
301
+ });
302
+ expect(newState.get()).toEqual(updatedUser);
303
+ });
304
+
305
+ it("测试存储解析错误处理", () => {
306
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
307
+
308
+ // 设置无效的JSON数据
309
+ localStorage.setItem("test-key", "invalid-json");
310
+
311
+ const state = createStorageState("test-key", "fallback", {
312
+ storageType: "local",
313
+ });
314
+
315
+ expect(state.get()).toBe("fallback");
316
+ expect(consoleSpy).toHaveBeenCalledWith(
317
+ expect.stringContaining('Failed to parse localStorage value for key "test-key"'),
318
+ expect.any(Error)
319
+ );
320
+
321
+ consoleSpy.mockRestore();
322
+ });
323
+
324
+ it("测试存储副作用函数", () => {
325
+ const mockSideEffect = vi.fn();
326
+ const state = createStorageState("test-key", "initial" as string, {
327
+ storageType: "local",
328
+ sideEffect: mockSideEffect,
329
+ });
330
+
331
+ state.set("updated");
332
+
333
+ expect(mockSideEffect).toHaveBeenCalledTimes(1);
334
+ expect(mockSideEffect).toHaveBeenCalledWith("updated");
335
+ expect(localStorage.getItem("test-key")).toBe('"updated"');
336
+ });
337
+
338
+ it("测试存储状态的transform功能", () => {
339
+ const state = createStorageState("test-key", "hello", {
340
+ storageType: "local",
341
+ transform: {
342
+ get: (str) => str.toUpperCase(),
343
+ set: (str) => str.toLowerCase(),
344
+ },
345
+ });
346
+
347
+ expect(state.get()).toBe("HELLO");
348
+
349
+ state.set("WORLD");
350
+ expect(state.get()).toBe("WORLD");
351
+ expect(localStorage.getItem("test-key")).toBe('"world"');
352
+ });
353
+
354
+ it("测试存储状态在React组件中的使用", async () => {
355
+ const state = createStorageState("component-key", "initial", {
356
+ storageType: "local",
357
+ });
358
+
359
+ function TestComponent() {
360
+ const [value, setValue] = state.use();
361
+ return (
362
+ <div>
363
+ <span data-testid="value">{value}</span>
364
+ <button onClick={() => setValue("component-updated")}>Update</button>
365
+ </div>
366
+ );
367
+ }
368
+
369
+ const { getByTestId, getByText } = render(<TestComponent />);
370
+ const valueLocator = getByTestId("value");
371
+ const buttonLocator = getByText("Update");
372
+
373
+ expect(valueLocator.element().textContent).toBe("initial");
374
+
375
+ await buttonLocator.click();
376
+
377
+ expect(valueLocator.element().textContent).toBe("component-updated");
378
+ expect(state.get()).toBe("component-updated");
379
+ expect(localStorage.getItem("component-key")).toBe('"component-updated"');
380
+ });
381
+
382
+ it("测试默认storageType为local", () => {
383
+ const state = createStorageState("default-key", "initial");
384
+
385
+ state.set("default-updated");
386
+
387
+ expect(localStorage.getItem("default-key")).toBe('"default-updated"');
388
+ expect(sessionStorage.getItem("default-key")).toBeNull();
389
+ });
390
+ });
@@ -200,3 +200,38 @@ export function createExternalState<T, U = T>(
200
200
  //@ts-expect-error ignore
201
201
  return { get, set, use, useGetter, __listeners: listeners };
202
202
  }
203
+
204
+ export interface StorageStateOptions<T, U> {
205
+ sideEffect?: (newState: T) => void;
206
+ transform?: Transform<T, U>;
207
+ storageType: "local" | "session";
208
+ }
209
+
210
+ export function createStorageState<T, U = T>(
211
+ key: string,
212
+ initialState: T,
213
+ options?: StorageStateOptions<T, U>
214
+ ) {
215
+ const { storageType = "local", sideEffect, transform } = options ?? {};
216
+ const storage = storageType === "local" ? localStorage : sessionStorage;
217
+ let _initState: T = initialState;
218
+ const storedValue = storage.getItem(key);
219
+ if (storedValue) {
220
+ try {
221
+ _initState = JSON.parse(storedValue);
222
+ } catch (error) {
223
+ console.warn(
224
+ `Failed to parse ${storageType}Storage value for key "${key}", using initial state:`,
225
+ error
226
+ );
227
+ _initState = initialState;
228
+ }
229
+ }
230
+ return createExternalState(_initState, {
231
+ sideEffect: (newState) => {
232
+ storage.setItem(key, JSON.stringify(newState));
233
+ sideEffect?.(newState);
234
+ },
235
+ transform,
236
+ });
237
+ }
@@ -32,7 +32,6 @@ function promiseTry<T, U extends unknown[]>(
32
32
  export const safePromiseTry = (() => {
33
33
  if (typeof Promise.try === "function") {
34
34
  return Promise.try.bind(Promise);
35
- } else {
36
- return promiseTry;
37
35
  }
36
+ return promiseTry;
38
37
  })();