@wwog/react 1.2.20 → 1.3.0

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
@@ -189,7 +189,9 @@ function UserList({ users }) {
189
189
 
190
190
  #### `<Clamp>` (v1.2.14+)
191
191
 
192
- A component for displaying text with a fixed number of lines, ellipsis, and optional extra content. Highly compatible without using webkit-box or JavaScript tricks.
192
+ > Removed in v1.3.0. The compatibility problem is too big, the desktop web page works well, h5 has a problem.
193
+
194
+ A component for displaying text with a fixed number of lines, ellipsis, and optional extra content.
193
195
 
194
196
  ```tsx
195
197
  import { Clamp } from "@wwog/react";
@@ -393,14 +395,19 @@ You can also use a container wrapper element:
393
395
 
394
396
  #### `createExternalState` (v1.2.9+, useGetter added in v1.2.13)
395
397
 
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.
398
+ > v1.2.21: Refactor the API to move sideeffects into options and enhance support for the transform interface
399
+ > v1.2.13: add useGetter
400
+
401
+ > 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
402
 
398
403
  ```tsx
399
404
  import { createExternalState } from "@wwog/react";
400
405
 
401
406
  // Create a global theme state
402
- const themeState = createExternalState("light", (newTheme, oldTheme) => {
403
- console.log(`Theme changed from ${oldTheme} to ${newTheme}`);
407
+ const themeState = createExternalState("light", {
408
+ sideEffect: (newTheme, oldTheme) => {
409
+ console.log(`Theme changed from ${oldTheme} to ${newTheme}`);
410
+ },
404
411
  });
405
412
 
406
413
  // Get or modify state from anywhere
@@ -429,16 +436,17 @@ function ReadOnlyThemeConsumer() {
429
436
  }
430
437
  ```
431
438
 
432
- - `createExternalState<T>(initialState, sideEffect?)`: Creates a state accessible outside components
439
+ - `createExternalState<T>(initialState, options?)`: Creates a state accessible outside components
440
+
433
441
  - `initialState`: Initial state value
434
- - `sideEffect`: Optional side effect function, called on state updates
442
+ - `options.sideEffect`: Optional side effect function, called on state updates
435
443
  - Returns an object with methods:
436
444
  - `get()`: Get the current state value
437
445
  - `set(newState)`: Update the state value
438
446
  - `use()`: React Hook, returns `[state, setState]` for using this state in components
439
447
  - `useGetter()`: React Hook that only returns the state value, useful when you only need to read the state
440
-
441
- Use cases:
448
+ - `options.transform`: - `get` - `set`
449
+ Use cases:
442
450
 
443
451
  - Global state management (themes, user settings, etc.)
444
452
  - Cross-component communication
package/dist/index.d.mts CHANGED
@@ -372,35 +372,6 @@ interface ToggleProps<T = boolean> {
372
372
  */
373
373
  declare const Toggle: <T>(props: ToggleProps<T>) => React$1.ReactNode;
374
374
 
375
- interface ClampProps {
376
- /**
377
- * @description 最大行数
378
- * @description_en maximum number of lines
379
- * @default 1
380
- */
381
- maxLine?: number;
382
- extraContent?: React$1.ReactNode;
383
- /**
384
- * @description 用于控制额外内容的高度,如果出现没有正常显示请调节此属性
385
- * @description_en used to control the height of the extra content. If it does not display normally, please adjust this property
386
- * @default 20
387
- */
388
- extraHeight?: number;
389
- /**
390
- * @description 显示的文本
391
- * @description_en text to be displayed
392
- */
393
- text: string;
394
- wrapperStyle?: React$1.CSSProperties;
395
- }
396
- /**
397
- * @description 用于固定行数,显示省略号且显示额外内容的组件。兼容性非常好,没有用到webkit-box和js。
398
- * @description_en used to fix the number of lines, display ellipsis and display extra content. The compatibility is very good, without using webkit-box and js.
399
- * @param props
400
- * @returns
401
- */
402
- declare const Clamp: FC<ClampProps>;
403
-
404
375
  interface ArrayRenderProps<T> {
405
376
  items: T[];
406
377
  renderItem: (item: T, index: number) => React$1.ReactNode;
@@ -492,49 +463,92 @@ type CreateStateListener<T> = (state: T) => void;
492
463
  * @param prevState The previous state value / 之前的状态值
493
464
  */
494
465
  type ExternalSideEffect<T> = (newState: T, prevState: T) => any | Promise<any>;
466
+ /**
467
+ * @en Transform functions for getting and setting state
468
+ * @zh 用于获取和设置状态的转换函数
469
+ * @template T The type of the state / 状态的类型
470
+ * @template U The transformed type for getting / 获取时的转换类型
471
+ */
472
+ interface Transform<T, U = T> {
473
+ /**
474
+ * @en Transform function for getting state
475
+ * @zh 获取状态时的转换函数
476
+ */
477
+ get?: (state: T) => U;
478
+ /**
479
+ * @en Transform function for setting state
480
+ * @zh 设置状态时的转换函数
481
+ */
482
+ set?: (value: U) => T;
483
+ }
484
+ /**
485
+ * @en Options for creating external state
486
+ * @zh 创建外部状态的选项
487
+ * @template T The type of the state / 状态的类型
488
+ * @template U The transformed type for getting / 获取时的转换类型
489
+ */
490
+ interface ExternalStateOptions<T, U = T> {
491
+ /**
492
+ * @en Side effect function to run after state changes
493
+ * @zh 状态变更后运行的副作用函数
494
+ */
495
+ sideEffect?: ExternalSideEffect<T>;
496
+ /**
497
+ * @en Transform functions for getting and setting state
498
+ * @zh 用于获取和设置状态的转换函数
499
+ */
500
+ transform?: Transform<T, U>;
501
+ }
495
502
  /**
496
503
  * @en External state management interface
497
504
  * @zh 外部状态管理接口
498
505
  * @template T The type of the state / 状态的类型
506
+ * @template U The transformed type for getting / 获取时的转换类型
499
507
  */
500
- interface ExternalState<T> {
508
+ interface ExternalState<T, U = T> {
501
509
  /**
502
510
  * @en Get the current state value
503
511
  * @zh 获取当前状态值
504
- * @returns The current state value / 当前状态值
512
+ * @returns The current state value (transformed if transform.get is provided) / 当前状态值(如果提供了 transform.get 则进行转换)
505
513
  */
506
- get: () => T;
514
+ get: () => U;
507
515
  /**
508
516
  * @en Set a new state value
509
517
  * @zh 设置新的状态值
510
- * @param newState The new state value / 新的状态值
518
+ * @param newState The new state value or a function that returns it / 新的状态值或返回新状态的函数
511
519
  */
512
- set: (newState: T) => void;
520
+ set: (newState: U | ((prevState: U) => U)) => void;
513
521
  /**
514
522
  * @en React Hook for using external state in components
515
523
  * @zh 在组件中使用外部状态的 React Hook
516
- * @returns Array containing current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
524
+ * @returns Array containing current钣金龙8国际唯一官网 current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
517
525
  */
518
- use: () => [T, (newState: T) => void];
526
+ use: () => [U, (newState: U | ((prevState: U) => U)) => void];
519
527
  /**
520
528
  * @zh use的变体,只获取value.
521
529
  * @en A variant of use that only gets the value.
522
530
  */
523
- useGetter: () => T;
531
+ useGetter: () => U;
524
532
  }
525
- interface ExternalWithKernel<T> extends ExternalState<T> {
533
+ interface ExternalWithKernel<T, U = T> extends ExternalState<T, U> {
526
534
  __listeners: CreateStateListener<T>[];
527
535
  }
528
536
  /**
529
537
  *
530
538
  * @example
531
539
  * ```tsx
532
- * // Create an app-level theme state
533
- * const themeState = createExternalState('light');
540
+ * // Create an app-level theme state with options
541
+ * const themeState = createExternalState('light', {
542
+ * sideEffect: (newState, prevState) => console.log(`Theme changed from ${prevState} to ${newState}`),
543
+ * transform: {
544
+ * get: (state) => state.toUpperCase(),
545
+ * set: (value) => value.toLowerCase()
546
+ * }
547
+ * });
534
548
  *
535
549
  * // Get or modify state outside components
536
- * console.log(themeState.get()); // 'light'
537
- * themeState.set('dark');
550
+ * console.log(themeState.get()); // 'LIGHT'
551
+ * themeState.set((prev) => prev === 'light' ? 'dark' : 'light'); // Toggle theme
538
552
  *
539
553
  * // Use state in components
540
554
  * function ThemeConsumer() {
@@ -542,7 +556,7 @@ interface ExternalWithKernel<T> extends ExternalState<T> {
542
556
  *
543
557
  * return (
544
558
  * <div className={theme}>
545
- * <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
559
+ * <button onClick={() => setTheme((prev) => prev === 'LIGHT' ? 'DARK' : 'LIGHT')}>
546
560
  * Toggle theme / 切换主题
547
561
  * </button>
548
562
  * </div>
@@ -550,7 +564,7 @@ interface ExternalWithKernel<T> extends ExternalState<T> {
550
564
  * }
551
565
  * ```
552
566
  */
553
- declare function createExternalState<T>(initialState: T | (() => T), sideEffect?: ExternalSideEffect<T>): ExternalState<T>;
567
+ declare function createExternalState<T, U = T>(initialState: T | (() => T), options?: ExternalStateOptions<T, U>): ExternalState<T, U>;
554
568
 
555
569
  /**
556
570
  * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
@@ -599,5 +613,5 @@ declare class Counter {
599
613
 
600
614
  declare const safePromiseTry: <T, U extends unknown[]>(callbackFn: (...args: U) => T | PromiseLike<T>, ...args: U) => Promise<Awaited<T>>;
601
615
 
602
- 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 };
616
+ export { ArrayRender, Counter, DateRender, False, If, Pipe, Scope, SizeBox, Styles, Switch, Toggle, True, When, childrenLoop, createExternalState, cx, formatDate, safePromiseTry, useControlled };
617
+ export type { ArrayRenderProps, 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 h,Children as J,Fragment as S,isValidElement as _,cloneElement as R,useEffect as W,useState as x,useCallback as H}from"react";function C(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 B=(t,n)=>t===n,E=t=>r.createElement(r.Fragment,null,t.children);E.displayName="Switch_Case";const w=t=>r.createElement(r.Fragment,null,t.children);w.displayName="Switch_Default";const y=t=>{const{value:n,compare:e=B,children:l,strict:o=!1}=t,a=new Set;let s=null,f=null,m=!1;return C(l,(i,c)=>{if(!r.isValidElement(i))throw new Error(`Switch Children only accepts valid React elements at index ${c}`);const u=i.type;if(u.displayName===E.displayName){const d=i.props;if(a.has(d.value))throw new Error(`Switch found duplicate Case value at index ${c}: ${JSON.stringify(d.value)}${o?" (detected in strict mode)":""}`);if(a.add(d.value),!s&&e(n,d.value)&&(s=d.children,o===!1))return!1}else if(u.displayName===w.displayName){if(m)throw new Error(`Switch can only have one Default child at index ${c}`);if(m=!0,f=i.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 ${c}`)}),r.createElement(r.Fragment,null,s??f)};y.displayName="Switch",y.Case=E,y.Default=w,y.createTyped=function(){return{Switch:y,Case:E,Default:w}};const N=t=>r.createElement(r.Fragment,null,t.children),v=({children:t})=>r.createElement(r.Fragment,null,t),F=t=>r.createElement(r.Fragment,null,t.children);N.displayName="If_Then",v.displayName="If_Else",F.displayName="If_ElseIf";const p=({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 s=a.type;if(s.displayName===N.displayName){if(e)throw new Error("If component can only have one Then child");e=a}else if(s.displayName===F.displayName)o.push(a);else if(s.displayName===v.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(s.displayName||s.name||s)}`)}),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};p.displayName="If",p.Then=N,p.ElseIf=F,p.Else=v,p.createTyped=function(){return{If:p,Then:N,ElseIf:F,Else:v}};const V=({condition:t,children:n})=>t?r.createElement(r.Fragment,null,n):null,Z=({condition:t,children:n})=>t===!1?r.createElement(r.Fragment,null,n):null,z=({all:t,any:n,none:e,children:l,fallback:o})=>h(()=>(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),L=({data:t,transform:n,render:e,fallback:l})=>{const o=h(()=>n.reduce((a,s)=>s(a),t),[t,n]);return o==null?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,e(o))},G=t=>{const{children:n,h:e,w:l,size:o,height:a,width:s,className:f}=t;return r.createElement("div",{style:{width:o||l||s,height:o||e||a,flexShrink:0},className:f},n)},U=({let:t,props:n,children:e,fallback:l})=>{const o=h(()=>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 M(...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 q=t=>typeof t=="object"&&!!t,b=({className:t,children:n,asWrapper:e=!1})=>{if(!n)return null;if(J.count(n)>1)return console.error("<Styles>: children has more than one child. Please check your code."),r.createElement(S,null,n);if(!t)return r.createElement(S,null,n);const l=typeof t=="string"?t:M(...Object.values(t));if(e)return r.createElement(e===!0?"div":e,{className:l},n);if(_(n)){const o=n;let a=o?.props?.className;return o?.type?.displayName===b.displayName&&q(a)&&(a=M(...Object.values(a))),R(n,{className:M(l,a)})}return console.error("<Styles>: children is not a valid React element. Please check your code."),r.createElement(S,null,n)};b.displayName="W/Styles";const K=t=>{const{index:n=0,options:e,next:l,render:o}=t;W(()=>{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,s]=x(n),f=()=>{s(m=>e.length?l?l(m,e):(m+1)%e.length:m)};return o(e[a],f)};function Q(t){const{items:n,renderItem:e,filter:l}=t;return n?r.createElement(S,null,n.map((o,a)=>l&&!l(o)?null:e(o,a))):(console.error("ArrayRender: items is null"),null)}function X({source:t,format:n,children:e}){const l=h(()=>{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=h(()=>l?n?n(l):l.toLocaleString():null,[l,n]);return!o||!e?null:r.createElement(r.Fragment,null,e(o))}const $="onChange",ee="value";function te(t){const{defaultValue:n,onBeforeChange:e,trigger:l=$,valuePropName:o=ee,props:a}=t,s=Object.prototype.hasOwnProperty.call(a,o),[f,m]=x(n),i=s?a[o]:f,c=h(()=>a[l],[a,l]),u=H(d=>{const g=typeof d=="function"?d(i):d;e&&e(g,i)===!1||(s||m(g),c&&c(g))},[s,e,i,c]);return[i,u]}function ne(t,...n){try{const e=t(...n);return e instanceof Promise?e:Promise.resolve(e)}catch(e){return Promise.reject(e)}}const I=typeof Promise.try=="function"?Promise.try.bind(Promise):ne;function re(t,n={}){let e=typeof t=="function"?t():t;const l=[],{sideEffect:o,transform:a}=n,s=()=>{const i=e;return a?.get?a.get(i):i},f=i=>{const c=e,u=a?.get?a.get(c):c;e=a?.set?a.set(typeof i=="function"?i(u):i):typeof i=="function"?i(u):i,l.forEach(d=>d(e)),o&&I(o,e,c).catch(d=>{console.error("Error in external state side effect, Please do it within side effects:",d)})},m=()=>{const[i,c]=r.useState(e);return r.useEffect(()=>(l.push(c),()=>{const u=l.indexOf(c);u>-1&&l.splice(u,1)}),[]),[a?.get?a.get(i):i,f]};return{get:s,set:f,use:m,useGetter:()=>{const[i]=m();return i},__listeners:l}}function le(t,n){const e=n||new Date,l=e.getFullYear(),o=e.getMonth()+1,a=e.getDate(),s=e.getHours(),f=e.getMinutes(),m=e.getSeconds(),i=e.getMilliseconds(),c=e.getDay(),u=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],d=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],g=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],A=["January","February","March","April","May","June","July","August","September","October","November","December"],P=d[c],D=u[c],T=o-1,Y=A[T],k=g[T],O={YY:l.toString().slice(2),YYYY:l.toString(),M:o.toString(),MM:o.toString().padStart(2,"0"),MMM:k,MMMM:Y,D:a.toString(),DD:a.toString().padStart(2,"0"),d:c.toString(),dd:D,ddd:D,dddd:P,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:i.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,j=>O[j])}class ae{count=0;next(){return this.count++}}export{Q as ArrayRender,ae as Counter,X as DateRender,Z as False,p as If,L as Pipe,U as Scope,G as SizeBox,b as Styles,y as Switch,K as Toggle,V as True,z as When,C as childrenLoop,re as createExternalState,M as cx,le as formatDate,I as safePromiseTry,te 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.3.0",
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",
@@ -2,4 +2,3 @@ export * from "./SizeBox";
2
2
  export * from "./Scope";
3
3
  export * from "./Styles";
4
4
  export * from "./Toggle";
5
- export * from "./Clamp";
@@ -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 = () => {
File without changes