@wwog/react 1.2.20 → 1.2.21

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
@@ -393,14 +393,19 @@ You can also use a container wrapper element:
393
393
 
394
394
  #### `createExternalState` (v1.2.9+, useGetter added in v1.2.13)
395
395
 
396
- 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.
396
+ > v1.2.21: Refactor the API to move sideeffects into options and enhance support for the transform interface
397
+ > v1.2.13: add useGetter
398
+
399
+ > 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.
397
400
 
398
401
  ```tsx
399
402
  import { createExternalState } from "@wwog/react";
400
403
 
401
404
  // Create a global theme state
402
- const themeState = createExternalState("light", (newTheme, oldTheme) => {
403
- console.log(`Theme changed from ${oldTheme} to ${newTheme}`);
405
+ const themeState = createExternalState("light", {
406
+ sideEffect: (newTheme, oldTheme) => {
407
+ console.log(`Theme changed from ${oldTheme} to ${newTheme}`);
408
+ },
404
409
  });
405
410
 
406
411
  // Get or modify state from anywhere
@@ -429,15 +434,17 @@ function ReadOnlyThemeConsumer() {
429
434
  }
430
435
  ```
431
436
 
432
- - `createExternalState<T>(initialState, sideEffect?)`: Creates a state accessible outside components
437
+ - `createExternalState<T>(initialState, options?)`: Creates a state accessible outside components
433
438
  - `initialState`: Initial state value
434
- - `sideEffect`: Optional side effect function, called on state updates
439
+ - `options.sideEffect`: Optional side effect function, called on state updates
435
440
  - Returns an object with methods:
436
441
  - `get()`: Get the current state value
437
442
  - `set(newState)`: Update the state value
438
443
  - `use()`: React Hook, returns `[state, setState]` for using this state in components
439
444
  - `useGetter()`: React Hook that only returns the state value, useful when you only need to read the state
440
-
445
+ - `options.transform`:
446
+ - `get`
447
+ - `set`
441
448
  Use cases:
442
449
 
443
450
  - Global state management (themes, user settings, etc.)
package/dist/index.d.mts CHANGED
@@ -492,49 +492,92 @@ type CreateStateListener<T> = (state: T) => void;
492
492
  * @param prevState The previous state value / 之前的状态值
493
493
  */
494
494
  type ExternalSideEffect<T> = (newState: T, prevState: T) => any | Promise<any>;
495
+ /**
496
+ * @en Transform functions for getting and setting state
497
+ * @zh 用于获取和设置状态的转换函数
498
+ * @template T The type of the state / 状态的类型
499
+ * @template U The transformed type for getting / 获取时的转换类型
500
+ */
501
+ interface Transform<T, U = T> {
502
+ /**
503
+ * @en Transform function for getting state
504
+ * @zh 获取状态时的转换函数
505
+ */
506
+ get?: (state: T) => U;
507
+ /**
508
+ * @en Transform function for setting state
509
+ * @zh 设置状态时的转换函数
510
+ */
511
+ set?: (value: U) => T;
512
+ }
513
+ /**
514
+ * @en Options for creating external state
515
+ * @zh 创建外部状态的选项
516
+ * @template T The type of the state / 状态的类型
517
+ * @template U The transformed type for getting / 获取时的转换类型
518
+ */
519
+ interface ExternalStateOptions<T, U = T> {
520
+ /**
521
+ * @en Side effect function to run after state changes
522
+ * @zh 状态变更后运行的副作用函数
523
+ */
524
+ sideEffect?: ExternalSideEffect<T>;
525
+ /**
526
+ * @en Transform functions for getting and setting state
527
+ * @zh 用于获取和设置状态的转换函数
528
+ */
529
+ transform?: Transform<T, U>;
530
+ }
495
531
  /**
496
532
  * @en External state management interface
497
533
  * @zh 外部状态管理接口
498
534
  * @template T The type of the state / 状态的类型
535
+ * @template U The transformed type for getting / 获取时的转换类型
499
536
  */
500
- interface ExternalState<T> {
537
+ interface ExternalState<T, U = T> {
501
538
  /**
502
539
  * @en Get the current state value
503
540
  * @zh 获取当前状态值
504
- * @returns The current state value / 当前状态值
541
+ * @returns The current state value (transformed if transform.get is provided) / 当前状态值(如果提供了 transform.get 则进行转换)
505
542
  */
506
- get: () => T;
543
+ get: () => U;
507
544
  /**
508
545
  * @en Set a new state value
509
546
  * @zh 设置新的状态值
510
- * @param newState The new state value / 新的状态值
547
+ * @param newState The new state value or a function that returns it / 新的状态值或返回新状态的函数
511
548
  */
512
- set: (newState: T) => void;
549
+ set: (newState: U | ((prevState: U) => U)) => void;
513
550
  /**
514
551
  * @en React Hook for using external state in components
515
552
  * @zh 在组件中使用外部状态的 React Hook
516
- * @returns Array containing current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
553
+ * @returns Array containing current钣金龙8国际唯一官网 current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
517
554
  */
518
- use: () => [T, (newState: T) => void];
555
+ use: () => [U, (newState: U | ((prevState: U) => U)) => void];
519
556
  /**
520
557
  * @zh use的变体,只获取value.
521
558
  * @en A variant of use that only gets the value.
522
559
  */
523
- useGetter: () => T;
560
+ useGetter: () => U;
524
561
  }
525
- interface ExternalWithKernel<T> extends ExternalState<T> {
562
+ interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
526
563
  __listeners: CreateStateListener<T>[];
527
564
  }
528
565
  /**
529
566
  *
530
567
  * @example
531
568
  * ```tsx
532
- * // Create an app-level theme state
533
- * const themeState = createExternalState('light');
569
+ * // Create an app-level theme state with options
570
+ * const themeState = createExternalState('light', {
571
+ * sideEffect: (newState, prevState) => console.log(`Theme changed from ${prevState} to ${newState}`),
572
+ * transform: {
573
+ * get: (state) => state.toUpperCase(),
574
+ * set: (value) => value.toLowerCase()
575
+ * }
576
+ * });
534
577
  *
535
578
  * // Get or modify state outside components
536
- * console.log(themeState.get()); // 'light'
537
- * themeState.set('dark');
579
+ * console.log(themeState.get()); // 'LIGHT'
580
+ * themeState.set((prev) => prev === 'light' ? 'dark' : 'light'); // Toggle theme
538
581
  *
539
582
  * // Use state in components
540
583
  * function ThemeConsumer() {
@@ -542,7 +585,7 @@ interface ExternalWithKernel<T> extends ExternalState<T> {
542
585
  *
543
586
  * return (
544
587
  * <div className={theme}>
545
- * <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
588
+ * <button onClick={() => setTheme((prev) => prev === 'LIGHT' ? 'DARK' : 'LIGHT')}>
546
589
  * Toggle theme / 切换主题
547
590
  * </button>
548
591
  * </div>
@@ -550,7 +593,7 @@ interface ExternalWithKernel<T> extends ExternalState<T> {
550
593
  * }
551
594
  * ```
552
595
  */
553
- declare function createExternalState<T>(initialState: T | (() => T), sideEffect?: ExternalSideEffect<T>): ExternalState<T>;
596
+ declare function createExternalState<T, U = T>(initialState: T | (() => T), options?: ExternalStateOptions<T, U>): ExternalState<T, U>;
554
597
 
555
598
  /**
556
599
  * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
@@ -600,4 +643,4 @@ declare class Counter {
600
643
  declare const safePromiseTry: <T, U extends unknown[]>(callbackFn: (...args: U) => T | PromiseLike<T>, ...args: U) => Promise<Awaited<T>>;
601
644
 
602
645
  export { ArrayRender, Clamp, Counter, DateRender, False, If, Pipe, Scope, SizeBox, Styles, Switch, Toggle, True, When, childrenLoop, createExternalState, cx, formatDate, safePromiseTry, useControlled };
603
- export type { ArrayRenderProps, ClampProps, CreateStateListener, CxInput, DateRenderProps, ElseIfProps, ElseProps, ExternalSideEffect, ExternalState, ExternalWithKernel, FalseProps, IfProps, PipeProps, ScopeProps, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
646
+ export type { ArrayRenderProps, ClampProps, CreateStateListener, CxInput, DateRenderProps, ElseIfProps, ElseProps, ExternalSideEffect, ExternalState, ExternalStateOptions, ExternalWithKernel, FalseProps, IfProps, PipeProps, ScopeProps, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, Transform, TrueProps, UseControlledOptions, WhenProps };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import r,{useMemo as y,Children as B,Fragment as w,isValidElement as W,cloneElement as J,useEffect as R,useState as T,useRef as _,useLayoutEffect as L,useCallback as Z}from"react";function P(t,n){if(t===void 0)return;let e=0;if(Array.isArray(t)){for(const l of t)if(n(l,e++)===!1)break}else n(t,e)}const z=(t,n)=>t===n,v=t=>r.createElement(r.Fragment,null,t.children);v.displayName="Switch_Case";const N=t=>r.createElement(r.Fragment,null,t.children);N.displayName="Switch_Default";const S=t=>{const{value:n,compare:e=z,children:l,strict:o=!1}=t,a=new Set;let i=null,c=null,u=!1;return P(l,(s,m)=>{if(!r.isValidElement(s))throw new Error(`Switch Children only accepts valid React elements at index ${m}`);const d=s.type;if(d.displayName===v.displayName){const f=s.props;if(a.has(f.value))throw new Error(`Switch found duplicate Case value at index ${m}: ${JSON.stringify(f.value)}${o?" (detected in strict mode)":""}`);if(a.add(f.value),!i&&e(n,f.value)&&(i=f.children,o===!1))return!1}else if(d.displayName===N.displayName){if(u)throw new Error(`Switch can only have one Default child at index ${m}`);if(u=!0,c=s.props.children,!o&&i)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(d.displayName||d.name||d)} at index ${m}`)}),r.createElement(r.Fragment,null,i??c)};S.displayName="Switch",S.Case=v,S.Default=N,S.createTyped=function(){return{Switch:S,Case:v,Default:N}};const b=t=>r.createElement(r.Fragment,null,t.children),M=({children:t})=>r.createElement(r.Fragment,null,t),F=t=>r.createElement(r.Fragment,null,t.children);b.displayName="If_Then",M.displayName="If_Else",F.displayName="If_ElseIf";const g=({condition:t,children:n})=>{let e=null,l=null;const o=[];if(r.Children.forEach(n,a=>{if(!r.isValidElement(a))throw new Error("If component only accepts valid React elements");const i=a.type;if(i.displayName===b.displayName){if(e)throw new Error("If component can only have one Then child");e=a}else if(i.displayName===F.displayName)o.push(a);else if(i.displayName===M.displayName){if(l)throw new Error("If component can only have one Else child");l=a}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(i.displayName||i.name||i)}`)}),t)return e?r.createElement(r.Fragment,null,e.props.children):null;for(const a of o)if(a.props.condition)return r.createElement(r.Fragment,null,a.props.children);return l?r.createElement(r.Fragment,null,l.props.children):null};g.displayName="If",g.Then=b,g.ElseIf=F,g.Else=M,g.createTyped=function(){return{If:g,Then:b,ElseIf:F,Else:M}};const I=({condition:t,children:n})=>t?r.createElement(r.Fragment,null,n):null,V=({condition:t,children:n})=>t===!1?r.createElement(r.Fragment,null,n):null,G=({all:t,any:n,none:e,children:l,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(a=>!a))),[t,n,e])?r.createElement(r.Fragment,null,l):r.createElement(r.Fragment,null,o||null),q=({data:t,transform:n,render:e,fallback:l})=>{const o=y(()=>n.reduce((a,i)=>i(a),t),[t,n]);return o==null?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,e(o))},K=t=>{const{children:n,h:e,w:l,size:o,height:a,width:i,className:c}=t;return r.createElement("div",{style:{width:o||l||i,height:o||e||a,flexShrink:0},className:c},n)},Q=({let:t,props:n,children:e,fallback:l})=>{const o=y(()=>typeof t=="function"?t(n):t,[t,n]);return!e||!Object.keys(o).length?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,e(o))};function x(...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(l=>n.add(l));else if(typeof e=="object")for(const[l,o]of Object.entries(e))o&&n.add(l)}return Array.from(n).join(" ")}const U=t=>typeof t=="object"&&!!t,A=({className:t,children:n,asWrapper:e=!1})=>{if(!n)return null;if(B.count(n)>1)return console.error("<Styles>: children has more than one child. Please check your code."),r.createElement(w,null,n);if(!t)return r.createElement(w,null,n);const l=typeof t=="string"?t:x(...Object.values(t));if(e)return r.createElement(e===!0?"div":e,{className:l},n);if(W(n)){const o=n;let a=o?.props?.className;return o?.type?.displayName===A.displayName&&U(a)&&(a=x(...Object.values(a))),J(n,{className:x(l,a)})}return console.error("<Styles>: children is not a valid React element. Please check your code."),r.createElement(w,null,n)};A.displayName="W/Styles";const X=t=>{const{index:n=0,options:e,next:l,render:o}=t;R(()=>{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[a,i]=T(n),c=()=>{i(u=>e.length?l?l(u,e):(u+1)%e.length:u)};return o(e[a],c)},$=t=>{const{maxLine:n=1,text:e,extraHeight:l=22,extraContent:o,wrapperStyle:a}=t,i=_(null),[c,u]=T(!1),s=y(()=>!(e==null||e===""),[e]),m=()=>{if(!i.current)return;const d=document.createElement("div");d.textContent=e;const f=getComputedStyle(i.current);if(d.style.width=f.width,d.style.fontSize=f.fontSize,d.style.lineHeight=f.lineHeight,d.style.wordBreak=f.wordBreak,d.style.visibility="hidden",a){const E=Object.keys(a).map(p=>p.replace(/[A-Z]/g,k=>`-${k.toLowerCase()}`));for(const p of E)d.style[p]=f[p]}document.body.appendChild(d);const h=parseInt(getComputedStyle(d).lineHeight)||20,C=d.offsetHeight,D=Math.round(C/h);document.body.removeChild(d),u(D>n)};return L(()=>{s&&m()},[n,e,s]),r.createElement(I,{condition:s},r.createElement("div",{ref:i,style:{overflow:"hidden",width:"100%",display:"flex",...a}},r.createElement("div",{style:{display:"-webkit-box",WebkitBoxOrient:"vertical",WebkitLineClamp:n,overflow:"hidden",wordBreak:"break-all"}},r.createElement(I,{condition:c},r.createElement("div",{style:{float:"right",height:"100%",marginBottom:-l}}),r.createElement("div",{style:{float:"right",clear:"both",height:l}},o)),e)))};function ee(t){const{items:n,renderItem:e,filter:l}=t;return n?r.createElement(w,null,n.map((o,a)=>l&&!l(o)?null:e(o,a))):(console.error("ArrayRender: items is null"),null)}function te({source:t,format:n,children:e}){const l=y(()=>{if(t instanceof Date)return t;if(typeof t=="string"||typeof t=="number"){const a=new Date(t);return isNaN(a.getTime())?null:a}return null},[t]),o=y(()=>l?n?n(l):l.toLocaleString():null,[l,n]);return!o||!e?null:r.createElement(r.Fragment,null,e(o))}const ne="onChange",re="value";function le(t){const{defaultValue:n,onBeforeChange:e,trigger:l=ne,valuePropName:o=re,props:a}=t,i=Object.prototype.hasOwnProperty.call(a,o),[c,u]=T(n),s=i?a[o]:c,m=y(()=>a[l],[a,l]),d=Z(f=>{const h=typeof f=="function"?f(s):f;e&&e(h,s)===!1||(i||u(h),m&&m(h))},[i,e,s,m]);return[s,d]}function ae(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):ae;function oe(t,n){let e=typeof t=="function"?t():t;const l=[],o=()=>e,a=c=>{const u=e;e=typeof c=="function"?c():c,l.forEach(s=>s(e)),n&&Y(n,e,u).catch(s=>{console.error("Error in external state side effect, Please do it within side effects:",s)})},i=()=>{const[c,u]=r.useState(e);return r.useEffect(()=>(l.push(u),()=>{const s=l.indexOf(u);s>-1&&l.splice(s,1)}),[]),[c,a]};return{get:o,set:a,use:i,useGetter:()=>{const[c]=i();return c},__listeners:l}}function ie(t,n){const e=n||new Date,l=e.getFullYear(),o=e.getMonth()+1,a=e.getDate(),i=e.getHours(),c=e.getMinutes(),u=e.getSeconds(),s=e.getMilliseconds(),m=e.getDay(),d=["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"],C=["January","February","March","April","May","June","July","August","September","October","November","December"],D=f[m],E=d[m],p=o-1,k=C[p],O=h[p],j={YY:l.toString().slice(2),YYYY:l.toString(),M:o.toString(),MM:o.toString().padStart(2,"0"),MMM:O,MMMM:k,D:a.toString(),DD:a.toString().padStart(2,"0"),d:m.toString(),dd:E,ddd:E,dddd:D,H:i.toString(),HH:i.toString().padStart(2,"0"),h:(i%12).toString(),hh:(i%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:i<12?"AM":"PM",a:i<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,H=>j[H])}class se{count=0;next(){return this.count++}}export{ee as ArrayRender,$ as Clamp,se as Counter,te as DateRender,V as False,g as If,q as Pipe,Q as Scope,K as SizeBox,A as Styles,S as Switch,X as Toggle,I as True,G as When,P as childrenLoop,oe as createExternalState,x as cx,ie as formatDate,Y as safePromiseTry,le as useControlled};
1
+ import r,{useMemo as y,Children as B,Fragment as w,isValidElement as W,cloneElement as J,useEffect as R,useState as I,useRef as _,useLayoutEffect as L,useCallback as Z}from"react";function P(t,n){if(t===void 0)return;let e=0;if(Array.isArray(t)){for(const a of t)if(n(a,e++)===!1)break}else n(t,e)}const z=(t,n)=>t===n,v=t=>r.createElement(r.Fragment,null,t.children);v.displayName="Switch_Case";const N=t=>r.createElement(r.Fragment,null,t.children);N.displayName="Switch_Default";const S=t=>{const{value:n,compare:e=z,children:a,strict:o=!1}=t,l=new Set;let i=null,f=null,m=!1;return P(a,(s,u)=>{if(!r.isValidElement(s))throw new Error(`Switch Children only accepts valid React elements at index ${u}`);const c=s.type;if(c.displayName===v.displayName){const d=s.props;if(l.has(d.value))throw new Error(`Switch found duplicate Case value at index ${u}: ${JSON.stringify(d.value)}${o?" (detected in strict mode)":""}`);if(l.add(d.value),!i&&e(n,d.value)&&(i=d.children,o===!1))return!1}else if(c.displayName===N.displayName){if(m)throw new Error(`Switch can only have one Default child at index ${u}`);if(m=!0,f=s.props.children,!o&&i)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(c.displayName||c.name||c)} at index ${u}`)}),r.createElement(r.Fragment,null,i??f)};S.displayName="Switch",S.Case=v,S.Default=N,S.createTyped=function(){return{Switch:S,Case:v,Default:N}};const b=t=>r.createElement(r.Fragment,null,t.children),M=({children:t})=>r.createElement(r.Fragment,null,t),F=t=>r.createElement(r.Fragment,null,t.children);b.displayName="If_Then",M.displayName="If_Else",F.displayName="If_ElseIf";const g=({condition:t,children:n})=>{let e=null,a=null;const o=[];if(r.Children.forEach(n,l=>{if(!r.isValidElement(l))throw new Error("If component only accepts valid React elements");const i=l.type;if(i.displayName===b.displayName){if(e)throw new Error("If component can only have one Then child");e=l}else if(i.displayName===F.displayName)o.push(l);else if(i.displayName===M.displayName){if(a)throw new Error("If component can only have one Else child");a=l}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(i.displayName||i.name||i)}`)}),t)return e?r.createElement(r.Fragment,null,e.props.children):null;for(const l of o)if(l.props.condition)return r.createElement(r.Fragment,null,l.props.children);return a?r.createElement(r.Fragment,null,a.props.children):null};g.displayName="If",g.Then=b,g.ElseIf=F,g.Else=M,g.createTyped=function(){return{If:g,Then:b,ElseIf:F,Else:M}};const T=({condition:t,children:n})=>t?r.createElement(r.Fragment,null,n):null,V=({condition:t,children:n})=>t===!1?r.createElement(r.Fragment,null,n):null,G=({all:t,any:n,none:e,children:a,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])?r.createElement(r.Fragment,null,a):r.createElement(r.Fragment,null,o||null),U=({data:t,transform:n,render:e,fallback:a})=>{const o=y(()=>n.reduce((l,i)=>i(l),t),[t,n]);return o==null?r.createElement(r.Fragment,null,a||null):r.createElement(r.Fragment,null,e(o))},q=t=>{const{children:n,h:e,w:a,size:o,height:l,width:i,className:f}=t;return r.createElement("div",{style:{width:o||a||i,height:o||e||l,flexShrink:0},className:f},n)},K=({let:t,props:n,children:e,fallback:a})=>{const o=y(()=>typeof t=="function"?t(n):t,[t,n]);return!e||!Object.keys(o).length?r.createElement(r.Fragment,null,a||null):r.createElement(r.Fragment,null,e(o))};function x(...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(a=>n.add(a));else if(typeof e=="object")for(const[a,o]of Object.entries(e))o&&n.add(a)}return Array.from(n).join(" ")}const Q=t=>typeof t=="object"&&!!t,A=({className:t,children:n,asWrapper:e=!1})=>{if(!n)return null;if(B.count(n)>1)return console.error("<Styles>: children has more than one child. Please check your code."),r.createElement(w,null,n);if(!t)return r.createElement(w,null,n);const a=typeof t=="string"?t:x(...Object.values(t));if(e)return r.createElement(e===!0?"div":e,{className:a},n);if(W(n)){const o=n;let l=o?.props?.className;return o?.type?.displayName===A.displayName&&Q(l)&&(l=x(...Object.values(l))),J(n,{className:x(a,l)})}return console.error("<Styles>: children is not a valid React element. Please check your code."),r.createElement(w,null,n)};A.displayName="W/Styles";const X=t=>{const{index:n=0,options:e,next:a,render:o}=t;R(()=>{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,i]=I(n),f=()=>{i(m=>e.length?a?a(m,e):(m+1)%e.length:m)};return o(e[l],f)},$=t=>{const{maxLine:n=1,text:e,extraHeight:a=22,extraContent:o,wrapperStyle:l}=t,i=_(null),[f,m]=I(!1),s=y(()=>!(e==null||e===""),[e]),u=()=>{if(!i.current)return;const c=document.createElement("div");c.textContent=e;const d=getComputedStyle(i.current);if(c.style.width=d.width,c.style.fontSize=d.fontSize,c.style.lineHeight=d.lineHeight,c.style.wordBreak=d.wordBreak,c.style.visibility="hidden",l){const E=Object.keys(l).map(p=>p.replace(/[A-Z]/g,k=>`-${k.toLowerCase()}`));for(const p of E)c.style[p]=d[p]}document.body.appendChild(c);const h=parseInt(getComputedStyle(c).lineHeight)||20,C=c.offsetHeight,D=Math.round(C/h);document.body.removeChild(c),m(D>n)};return L(()=>{s&&u()},[n,e,s]),r.createElement(T,{condition:s},r.createElement("div",{ref:i,style:{overflow:"hidden",width:"100%",display:"flex",...l}},r.createElement("div",{style:{display:"-webkit-box",WebkitBoxOrient:"vertical",WebkitLineClamp:n,overflow:"hidden",wordBreak:"break-all"}},r.createElement(T,{condition:f},r.createElement("div",{style:{float:"right",height:"100%",marginBottom:-a}}),r.createElement("div",{style:{float:"right",clear:"both",height:a}},o)),e)))};function ee(t){const{items:n,renderItem:e,filter:a}=t;return n?r.createElement(w,null,n.map((o,l)=>a&&!a(o)?null:e(o,l))):(console.error("ArrayRender: items is null"),null)}function te({source:t,format:n,children:e}){const a=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(()=>a?n?n(a):a.toLocaleString():null,[a,n]);return!o||!e?null:r.createElement(r.Fragment,null,e(o))}const ne="onChange",re="value";function le(t){const{defaultValue:n,onBeforeChange:e,trigger:a=ne,valuePropName:o=re,props:l}=t,i=Object.prototype.hasOwnProperty.call(l,o),[f,m]=I(n),s=i?l[o]:f,u=y(()=>l[a],[l,a]),c=Z(d=>{const h=typeof d=="function"?d(s):d;e&&e(h,s)===!1||(i||m(h),u&&u(h))},[i,e,s,u]);return[s,c]}function ae(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):ae;function oe(t,n={}){let e=typeof t=="function"?t():t;const a=[],{sideEffect:o,transform:l}=n,i=()=>{const s=e;return l?.get?l.get(s):s},f=s=>{const u=e,c=l?.get?l.get(u):u;e=l?.set?l.set(typeof s=="function"?s(c):s):typeof s=="function"?s(c):s,a.forEach(d=>d(e)),o&&Y(o,e,u).catch(d=>{console.error("Error in external state side effect, Please do it within side effects:",d)})},m=()=>{const[s,u]=r.useState(e);return r.useEffect(()=>(a.push(u),()=>{const c=a.indexOf(u);c>-1&&a.splice(c,1)}),[]),[l?.get?l.get(s):s,f]};return{get:i,set:f,use:m,useGetter:()=>{const[s]=m();return s},__listeners:a}}function ie(t,n){const e=n||new Date,a=e.getFullYear(),o=e.getMonth()+1,l=e.getDate(),i=e.getHours(),f=e.getMinutes(),m=e.getSeconds(),s=e.getMilliseconds(),u=e.getDay(),c=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],d=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],h=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],C=["January","February","March","April","May","June","July","August","September","October","November","December"],D=d[u],E=c[u],p=o-1,k=C[p],O=h[p],j={YY:a.toString().slice(2),YYYY:a.toString(),M:o.toString(),MM:o.toString().padStart(2,"0"),MMM:O,MMMM:k,D:l.toString(),DD:l.toString().padStart(2,"0"),d:u.toString(),dd:E,ddd:E,dddd:D,H:i.toString(),HH:i.toString().padStart(2,"0"),h:(i%12).toString(),hh:(i%12).toString().padStart(2,"0"),m:f.toString(),mm:f.toString().padStart(2,"0"),s:m.toString(),ss:m.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 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,H=>j[H])}class se{count=0;next(){return this.count++}}export{ee as ArrayRender,$ as Clamp,se as Counter,te as DateRender,V as False,g as If,U as Pipe,K as Scope,q as SizeBox,A as Styles,S as Switch,X as Toggle,T as True,G as When,P as childrenLoop,oe as createExternalState,x as cx,ie 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.2.20",
3
+ "version": "1.2.21",
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",
@@ -123,7 +123,9 @@ describe("createExternalState", () => {
123
123
  it("测试副作用函数", () => {
124
124
  const mockSideEffect = vi.fn((...args) => void 0);
125
125
  const initialState: string = "initial";
126
- const state = createExternalState(initialState, mockSideEffect);
126
+ const state = createExternalState(initialState, {
127
+ sideEffect: mockSideEffect,
128
+ });
127
129
  state.set("updated");
128
130
  expect(mockSideEffect).toHaveBeenCalledTimes(1);
129
131
  expect(mockSideEffect).toHaveBeenCalledWith("updated", initialState);
@@ -135,7 +137,9 @@ describe("createExternalState", () => {
135
137
  it("测试异步副作用函数", async () => {
136
138
  const mockAsyncSideEffect = vi.fn().mockResolvedValue(undefined);
137
139
  const initialState: string = "initial";
138
- const state = createExternalState(initialState, mockAsyncSideEffect);
140
+ const state = createExternalState(initialState, {
141
+ sideEffect: mockAsyncSideEffect,
142
+ });
139
143
 
140
144
  state.set("updated");
141
145
  expect(mockAsyncSideEffect).toHaveBeenCalledTimes(1);
@@ -181,4 +185,37 @@ describe("createExternalState", () => {
181
185
  expect(ageLocator.element().textContent).toBe("35");
182
186
  expect(state.get()).toEqual({ name: "王五", age: 35 });
183
187
  });
188
+
189
+ it("测试transform转换功能", async () => {
190
+ const state = createExternalState("hello", {
191
+ transform: {
192
+ get: (str) => str.toUpperCase(),
193
+ set: (str) => str.toLowerCase(),
194
+ },
195
+ });
196
+
197
+ expect(state.get()).toBe("HELLO");
198
+
199
+ state.set("WORLD");
200
+ expect(state.get()).toBe("WORLD");
201
+
202
+ function TestComponent() {
203
+ const [value, setValue] = state.use();
204
+ return (
205
+ <div>
206
+ <span data-testid="value">{value}</span>
207
+ <button data-testid="update" onClick={() => setValue("TEST")}>
208
+ Update
209
+ </button>
210
+ </div>
211
+ );
212
+ }
213
+
214
+ const { getByTestId } = render(<TestComponent />);
215
+ expect(getByTestId("value").element().textContent).toBe("WORLD");
216
+
217
+ await getByTestId("update").click();
218
+ expect(getByTestId("value").element().textContent).toBe("TEST");
219
+ expect(state.get()).toBe("TEST");
220
+ });
184
221
  });
@@ -20,41 +20,80 @@ export type ExternalSideEffect<T> = (
20
20
  prevState: T
21
21
  ) => any | Promise<any>;
22
22
 
23
+ /**
24
+ * @en Transform functions for getting and setting state
25
+ * @zh 用于获取和设置状态的转换函数
26
+ * @template T The type of the state / 状态的类型
27
+ * @template U The transformed type for getting / 获取时的转换类型
28
+ */
29
+ export interface Transform<T, U = T> {
30
+ /**
31
+ * @en Transform function for getting state
32
+ * @zh 获取状态时的转换函数
33
+ */
34
+ get?: (state: T) => U;
35
+ /**
36
+ * @en Transform function for setting state
37
+ * @zh 设置状态时的转换函数
38
+ */
39
+ set?: (value: U) => T;
40
+ }
41
+
42
+ /**
43
+ * @en Options for creating external state
44
+ * @zh 创建外部状态的选项
45
+ * @template T The type of the state / 状态的类型
46
+ * @template U The transformed type for getting / 获取时的转换类型
47
+ */
48
+ export interface ExternalStateOptions<T, U = T> {
49
+ /**
50
+ * @en Side effect function to run after state changes
51
+ * @zh 状态变更后运行的副作用函数
52
+ */
53
+ sideEffect?: ExternalSideEffect<T>;
54
+ /**
55
+ * @en Transform functions for getting and setting state
56
+ * @zh 用于获取和设置状态的转换函数
57
+ */
58
+ transform?: Transform<T, U>;
59
+ }
60
+
23
61
  /**
24
62
  * @en External state management interface
25
63
  * @zh 外部状态管理接口
26
64
  * @template T The type of the state / 状态的类型
65
+ * @template U The transformed type for getting / 获取时的转换类型
27
66
  */
28
- export interface ExternalState<T> {
67
+ export interface ExternalState<T, U = T> {
29
68
  /**
30
69
  * @en Get the current state value
31
70
  * @zh 获取当前状态值
32
- * @returns The current state value / 当前状态值
71
+ * @returns The current state value (transformed if transform.get is provided) / 当前状态值(如果提供了 transform.get 则进行转换)
33
72
  */
34
- get: () => T;
73
+ get: () => U;
35
74
 
36
75
  /**
37
76
  * @en Set a new state value
38
77
  * @zh 设置新的状态值
39
- * @param newState The new state value / 新的状态值
78
+ * @param newState The new state value or a function that returns it / 新的状态值或返回新状态的函数
40
79
  */
41
- set: (newState: T) => void;
80
+ set: (newState: U | ((prevState: U) => U)) => void;
42
81
 
43
82
  /**
44
83
  * @en React Hook for using external state in components
45
84
  * @zh 在组件中使用外部状态的 React Hook
46
- * @returns Array containing current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
85
+ * @returns Array containing current钣金龙8国际唯一官网 current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
47
86
  */
48
- use: () => [T, (newState: T) => void];
87
+ use: () => [U, (newState: U | ((prevState: U) => U)) => void];
49
88
 
50
89
  /**
51
90
  * @zh use的变体,只获取value.
52
91
  * @en A variant of use that only gets the value.
53
92
  */
54
- useGetter: () => T;
93
+ useGetter: () => U;
55
94
  }
56
95
 
57
- export interface ExternalWithKernel<T> extends ExternalState<T> {
96
+ export interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
58
97
  __listeners: CreateStateListener<T>[];
59
98
  }
60
99
 
@@ -62,12 +101,18 @@ export interface ExternalWithKernel<T> extends ExternalState<T> {
62
101
  *
63
102
  * @example
64
103
  * ```tsx
65
- * // Create an app-level theme state
66
- * const themeState = createExternalState('light');
104
+ * // Create an app-level theme state with options
105
+ * const themeState = createExternalState('light', {
106
+ * sideEffect: (newState, prevState) => console.log(`Theme changed from ${prevState} to ${newState}`),
107
+ * transform: {
108
+ * get: (state) => state.toUpperCase(),
109
+ * set: (value) => value.toLowerCase()
110
+ * }
111
+ * });
67
112
  *
68
113
  * // Get or modify state outside components
69
- * console.log(themeState.get()); // 'light'
70
- * themeState.set('dark');
114
+ * console.log(themeState.get()); // 'LIGHT'
115
+ * themeState.set((prev) => prev === 'light' ? 'dark' : 'light'); // Toggle theme
71
116
  *
72
117
  * // Use state in components
73
118
  * function ThemeConsumer() {
@@ -75,7 +120,7 @@ export interface ExternalWithKernel<T> extends ExternalState<T> {
75
120
  *
76
121
  * return (
77
122
  * <div className={theme}>
78
- * <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
123
+ * <button onClick={() => setTheme((prev) => prev === 'LIGHT' ? 'DARK' : 'LIGHT')}>
79
124
  * Toggle theme / 切换主题
80
125
  * </button>
81
126
  * </div>
@@ -83,22 +128,39 @@ export interface ExternalWithKernel<T> extends ExternalState<T> {
83
128
  * }
84
129
  * ```
85
130
  */
86
- export function createExternalState<T>(
131
+ export function createExternalState<T, U = T>(
87
132
  initialState: T | (() => T),
88
- sideEffect?: ExternalSideEffect<T>
89
- ): ExternalState<T> {
133
+ options: ExternalStateOptions<T, U> = {}
134
+ ): ExternalState<T, U> {
90
135
  let state: T =
91
136
  typeof initialState === "function"
92
137
  ? (initialState as () => T)()
93
138
  : initialState;
94
139
 
95
140
  const listeners: CreateStateListener<T>[] = [];
141
+ const { sideEffect, transform } = options;
96
142
 
97
- const get = () => state;
143
+ const get = () => {
144
+ const currentState = state;
145
+ return transform?.get
146
+ ? transform.get(currentState)
147
+ : (currentState as unknown as U);
148
+ };
98
149
 
99
- const set = (newState: T | (() => T)) => {
150
+ const set = (newState: U | ((prevState: U) => U)) => {
100
151
  const prevState = state;
101
- state = typeof newState === "function" ? (newState as () => T)() : newState;
152
+ const transformedPrevState = transform?.get
153
+ ? transform.get(prevState)
154
+ : (prevState as unknown as U);
155
+ state = transform?.set
156
+ ? transform.set(
157
+ typeof newState === "function"
158
+ ? (newState as (prev: U) => U)(transformedPrevState)
159
+ : newState
160
+ )
161
+ : ((typeof newState === "function"
162
+ ? (newState as (prev: U) => U)(transformedPrevState)
163
+ : newState) as unknown as T);
102
164
 
103
165
  listeners.forEach((listener) => listener(state));
104
166
  if (sideEffect) {
@@ -112,7 +174,7 @@ export function createExternalState<T>(
112
174
  };
113
175
 
114
176
  const use = () => {
115
- const [localState, setLocalState] = React.useState(state);
177
+ const [localState, setLocalState] = React.useState<T>(state);
116
178
 
117
179
  React.useEffect(() => {
118
180
  listeners.push(setLocalState);
@@ -124,7 +186,10 @@ export function createExternalState<T>(
124
186
  };
125
187
  }, []);
126
188
 
127
- return [localState, set] as [T, (newState: T) => void];
189
+ return [
190
+ transform?.get ? transform.get(localState) : (localState as unknown as U),
191
+ set,
192
+ ] as [U, (newState: U | ((prevState: U) => U)) => void];
128
193
  };
129
194
 
130
195
  const useGetter = () => {