@wwog/react 1.2.8 → 1.2.10

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
@@ -318,7 +318,7 @@ Categorically write styles and basic string styles, with built-in functionality
318
318
 
319
319
  ```tsx
320
320
  import { Styles } from "@wwog/react";
321
- import clazz from './index.module.css'
321
+ import clazz from "./index.module.css";
322
322
 
323
323
  function Example() {
324
324
  return (
@@ -367,6 +367,52 @@ You can also use a container wrapper element:
367
367
 
368
368
  > Internal functions used by some components, which can also be used if needed
369
369
 
370
+ #### `createExternalState` (v1.2.9+)
371
+
372
+ 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.
373
+
374
+ ```tsx
375
+ import { createExternalState } from "@wwog/react";
376
+
377
+ // Create a global theme state
378
+ const themeState = createExternalState("light", (newTheme, oldTheme) => {
379
+ console.log(`Theme changed from ${oldTheme} to ${newTheme}`);
380
+ });
381
+
382
+ // Get or modify state from anywhere
383
+ console.log(themeState.get()); // 'light'
384
+ themeState.set("dark");
385
+
386
+ // Use the state in components
387
+ function ThemeConsumer() {
388
+ const [theme, setTheme] = themeState.use();
389
+
390
+ return (
391
+ <div className={theme}>
392
+ Current theme: {theme}
393
+ <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
394
+ Toggle theme
395
+ </button>
396
+ </div>
397
+ );
398
+ }
399
+ ```
400
+
401
+ - `createExternalState<T>(initialState, sideEffect?)`: Creates a state accessible outside components
402
+ - `initialState`: Initial state value
403
+ - `sideEffect`: Optional side effect function, called on state updates
404
+ - Returns an object with methods:
405
+ - `get()`: Get the current state value
406
+ - `set(newState)`: Update the state value
407
+ - `use()`: React Hook, returns `[state, setState]` for using this state in components
408
+
409
+ Use cases:
410
+
411
+ - Global state management (themes, user settings, etc.)
412
+ - Cross-component communication
413
+ - Reactive state in services or utility classes
414
+ - Sharing state with non-React code
415
+
370
416
  #### `formatDate`
371
417
 
372
418
  A relatively standard date formatting function
@@ -379,6 +425,10 @@ Interruptible child node traversal, enabling some branch processes to have ultim
379
425
 
380
426
  Incrementally class
381
427
 
428
+ #### `safePromiseTry` (v1.2.10+)
429
+
430
+ Support `Promise.try` Use `Promise.try`, otherwise use internal implementation
431
+
382
432
  #### `cx` (v1.2.5+)
383
433
 
384
434
  An efficient CSS class name merging utility function, similar to `clsx` or `classnames`, but automatically removes duplicate class names.
package/dist/index.d.mts CHANGED
@@ -449,6 +449,78 @@ interface UseControlledOptions<T> {
449
449
  }
450
450
  declare function useControlled<T>(options: UseControlledOptions<T>): [T, Dispatch<React.SetStateAction<T>>];
451
451
 
452
+ /**
453
+ * @en Callback function type for state change listeners
454
+ * @zh 状态变更监听器的回调函数类型
455
+ * @template T The type of the state / 状态的类型
456
+ */
457
+ type CreateStateListener<T> = (state: T) => void;
458
+ /**
459
+ * @zh 如果需要在变更状态时执行副作用,可以传入函数,对于异步函数,会在更改状态后执行,不会阻塞状态更新, 尽可能在外部使用useEffect处理异步副作用
460
+ * @en If you need to perform side effects when changing the state, you can pass a function. For asynchronous functions, it will be executed after the state changes without blocking the state update, so it's best to use useEffect for handling asynchronous side effects.
461
+ * @template T The type of the state / 状态的类型
462
+ * @param newState The new state value / 新的状态值
463
+ * @param prevState The previous state value / 之前的状态值
464
+ */
465
+ type ExternalSideEffect<T> = (newState: T, prevState: T) => void | Promise<void>;
466
+ /**
467
+ * @en External state management interface
468
+ * @zh 外部状态管理接口
469
+ * @template T The type of the state / 状态的类型
470
+ */
471
+ interface ExternalState<T> {
472
+ /**
473
+ * @en Get the current state value
474
+ * @zh 获取当前状态值
475
+ * @returns The current state value / 当前状态值
476
+ */
477
+ get: () => T;
478
+ /**
479
+ * @en Set a new state value
480
+ * @zh 设置新的状态值
481
+ * @param newState The new state value / 新的状态值
482
+ */
483
+ set: (newState: T) => void;
484
+ /**
485
+ * @en React Hook for using external state in components
486
+ * @zh 在组件中使用外部状态的 React Hook
487
+ * @returns Array containing current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
488
+ */
489
+ use: () => [T, (newState: T) => void];
490
+ }
491
+ /**
492
+ *
493
+ * @example
494
+ * ```tsx
495
+ * // Create an app-level theme state
496
+ * const themeState = createExternalState('light');
497
+ *
498
+ * // Get or modify state outside components
499
+ * console.log(themeState.get()); // 'light'
500
+ * themeState.set('dark');
501
+ *
502
+ * // Use state in components
503
+ * function ThemeConsumer() {
504
+ * const [theme, setTheme] = themeState.use();
505
+ *
506
+ * return (
507
+ * <div className={theme}>
508
+ * <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
509
+ * Toggle theme / 切换主题
510
+ * </button>
511
+ * </div>
512
+ * );
513
+ * }
514
+ * ```
515
+ */
516
+ declare function createExternalState<T>(initialState: T, sideEffect?: ExternalSideEffect<T>): ExternalState<T>;
517
+
518
+ /**
519
+ * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
520
+ * @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
521
+ */
522
+ declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
523
+
452
524
  /**
453
525
  * @param schema
454
526
  * @example
@@ -488,11 +560,7 @@ declare class Counter {
488
560
  next(): number;
489
561
  }
490
562
 
491
- /**
492
- * @description 性能优化,替代 React.Children.forEach, 回调可以返回 false 来中断循环
493
- * @description_en Replace React.Children.forEach, the callback can return false to interrupt the loop
494
- */
495
- declare function childrenLoop(children: React$1.ReactNode | undefined, callback: (child: React$1.ReactNode, index: number) => boolean | void): void;
563
+ declare const safePromiseTry: <T, U extends unknown[]>(callbackFn: (...args: U) => T | PromiseLike<T>, ...args: U) => Promise<Awaited<T>>;
496
564
 
497
- export { ArrayRender, Counter, DateRender, False, If, Pipe, Scope, SizeBox, Styles, Switch, Toggle, True, When, childrenLoop, cx, formatDate, useControlled };
498
- export type { ArrayRenderProps, CxInput, DateRenderProps, ElseIfProps, ElseProps, FalseProps, IfProps, PipeProps, ScopeProps, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
565
+ export { ArrayRender, Counter, DateRender, False, If, Pipe, Scope, SizeBox, Styles, Switch, Toggle, True, When, childrenLoop, createExternalState, cx, formatDate, safePromiseTry, useControlled };
566
+ export type { ArrayRenderProps, CreateStateListener, CxInput, DateRenderProps, ElseIfProps, ElseProps, ExternalSideEffect, ExternalState, FalseProps, IfProps, PipeProps, ScopeProps, StylesDescriptor, StylesProps, StylesType, SwitchCaseProps, SwitchDefaultProps, SwitchProps, ThenProps, ToggleProps, TrueProps, UseControlledOptions, WhenProps };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import r,{useMemo as h,Children as J,Fragment as S,isValidElement as P,cloneElement as R,useEffect as W,useState as I,useCallback as H}from"react";function T(e,n){if(e===void 0)return;let t=0;if(Array.isArray(e)){for(const l of e)if(n(l,t++)===!1)break}else n(e,t)}const _=(e,n)=>e===n,E=e=>r.createElement(r.Fragment,null,e.children);E.displayName="Switch_Case";const w=e=>r.createElement(r.Fragment,null,e.children);w.displayName="Switch_Default";const y=e=>{const{value:n,compare:t=_,children:l,strict:a=!1}=e,o=new Set;let s=null,c=null,u=!1;return T(l,(d,i)=>{if(!r.isValidElement(d))throw new Error(`Switch Children only accepts valid React elements at index ${i}`);const f=d.type;if(f.displayName===E.displayName){const m=d.props;if(o.has(m.value))throw new Error(`Switch found duplicate Case value at index ${i}: ${JSON.stringify(m.value)}${a?" (detected in strict mode)":""}`);if(o.add(m.value),!s&&t(n,m.value)&&(s=m.children,a===!1))return!1}else if(f.displayName===w.displayName){if(u)throw new Error(`Switch can only have one Default child at index ${i}`);if(u=!0,c=d.props.children,!a&&s)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(f.displayName||f.name||f)} at index ${i}`)}),r.createElement(r.Fragment,null,s??c)};y.displayName="Switch",y.Case=E,y.Default=w,y.createTyped=function(){return{Switch:y,Case:E,Default:w}};const N=e=>r.createElement(r.Fragment,null,e.children),v=({children:e})=>r.createElement(r.Fragment,null,e),F=e=>r.createElement(r.Fragment,null,e.children);N.displayName="If_Then",v.displayName="If_Else",F.displayName="If_ElseIf";const p=({condition:e,children:n})=>{let t=null,l=null;const a=[];if(r.Children.forEach(n,o=>{if(!r.isValidElement(o))throw new Error("If component only accepts valid React elements");const s=o.type;if(s.displayName===N.displayName){if(t)throw new Error("If component can only have one Then child");t=o}else if(s.displayName===F.displayName)a.push(o);else if(s.displayName===v.displayName){if(l)throw new Error("If component can only have one Else child");l=o}else throw new Error(`If component only accepts 'Then', 'ElseIf', or 'Else' elements as children, found: ${String(s.displayName||s.name||s)}`)}),e)return t?r.createElement(r.Fragment,null,t.props.children):null;for(const o of a)if(o.props.condition)return r.createElement(r.Fragment,null,o.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 B=({condition:e,children:n})=>e?r.createElement(r.Fragment,null,n):null,V=({condition:e,children:n})=>e===!1?r.createElement(r.Fragment,null,n):null,Z=({all:e,any:n,none:t,children:l,fallback:a})=>h(()=>(e&&(n||t)&&console.warn('When: Multiple condition types (all, any, none) provided; "all" takes precedence.'),!!(e&&e.length>0&&e.every(Boolean)||n&&n.length>0&&n.some(Boolean)||t&&t.length>0&&t.every(o=>!o))),[e,n,t])?r.createElement(r.Fragment,null,l):r.createElement(r.Fragment,null,a||null),z=({data:e,transform:n,render:t,fallback:l})=>{const a=h(()=>n.reduce((o,s)=>s(o),e),[e,n]);return a==null?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,t(a))},L=e=>{const{children:n,h:t,w:l,size:a,height:o,width:s,className:c}=e;return r.createElement("div",{style:{width:a||l||s,height:a||t||o,flexShrink:0},className:c},n)},q=({let:e,props:n,children:t,fallback:l})=>{const a=h(()=>typeof e=="function"?e(n):e,[e,n]);return!t||!Object.keys(a).length?r.createElement(r.Fragment,null,l||null):r.createElement(r.Fragment,null,t(a))};function M(...e){const n=new Set;for(const t of e)if(t){if(typeof t=="string")n.add(t);else if(Array.isArray(t))t.forEach(l=>n.add(l));else if(typeof t=="object")for(const[l,a]of Object.entries(t))a&&n.add(l)}return Array.from(n).join(" ")}const G=e=>typeof e=="object"&&!!e,b=({className:e,children:n,asWrapper:t=!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(!e)return r.createElement(S,null,n);const l=typeof e=="string"?e:M(...Object.values(e));if(t)return r.createElement(t===!0?"div":t,{className:l},n);if(P(n)){const a=n;let o=a?.props?.className;return a?.type?.displayName===b.displayName&&G(o)&&(o=M(...Object.values(o))),R(n,{className:M(l,o)})}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=e=>{const{index:n=0,options:t,next:l,render:a}=e;W(()=>{if(t.length<n+1)throw new Error(`Index ${n} is out of bounds for options array of length ${t.length}. Defaulting to first option.`)},[n,t]);const[o,s]=I(n),c=()=>{s(u=>t.length?l?l(u,t):(u+1)%t.length:u)};return a(t[o],c)};function Q(e){const{items:n,renderItem:t,filter:l}=e;return n?r.createElement(S,null,n.map((a,o)=>l&&!l(a)?null:t(a,o))):(console.error("ArrayRender: items is null"),null)}function U({source:e,format:n,children:t}){const l=h(()=>{if(e instanceof Date)return e;if(typeof e=="string"||typeof e=="number"){const o=new Date(e);return isNaN(o.getTime())?null:o}return null},[e]),a=h(()=>l?n?n(l):l.toLocaleString():null,[l,n]);return!a||!t?null:r.createElement(r.Fragment,null,t(a))}const X="onChange",$="value";function ee(e){const{defaultValue:n,onBeforeChange:t,trigger:l=X,valuePropName:a=$,props:o}=e,s=Object.prototype.hasOwnProperty.call(o,a),[c,u]=I(n),d=s?o[a]:c,i=h(()=>o[l],[o,l]),f=H(m=>{const g=typeof m=="function"?m(d):m;t&&t(g,d)===!1||(s||u(g),i&&i(g))},[s,t,d,i]);return[d,f]}function te(e,n){const t=n||new Date,l=t.getFullYear(),a=t.getMonth()+1,o=t.getDate(),s=t.getHours(),c=t.getMinutes(),u=t.getSeconds(),d=t.getMilliseconds(),i=t.getDay(),f=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],m=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],g=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],A=["January","February","March","April","May","June","July","August","September","October","November","December"],Y=m[i],D=f[i],C=a-1,x=A[C],k=g[C],O={YY:l.toString().slice(2),YYYY:l.toString(),M:a.toString(),MM:a.toString().padStart(2,"0"),MMM:k,MMMM:x,D:o.toString(),DD:o.toString().padStart(2,"0"),d:i.toString(),dd:D,ddd:D,dddd:Y,H:s.toString(),HH:s.toString().padStart(2,"0"),h:(s%12).toString(),hh:(s%12).toString().padStart(2,"0"),m:c.toString(),mm:c.toString().padStart(2,"0"),s:u.toString(),ss:u.toString().padStart(2,"0"),SSS:d.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,j=>O[j])}class ne{count=0;next(){return this.count++}}export{Q as ArrayRender,ne as Counter,U as DateRender,V as False,p as If,z as Pipe,q as Scope,L as SizeBox,b as Styles,y as Switch,K as Toggle,B as True,Z as When,T as childrenLoop,M as cx,te as formatDate,ee as useControlled};
1
+ import r,{useMemo as p,Children as J,Fragment as S,isValidElement as R,cloneElement as W,useEffect as H,useState as x,useCallback as _}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,c=null,i=!1;return C(l,(d,u)=>{if(!r.isValidElement(d))throw new Error(`Switch Children only accepts valid React elements at index ${u}`);const m=d.type;if(m.displayName===E.displayName){const f=d.props;if(a.has(f.value))throw new Error(`Switch found duplicate Case value at index ${u}: ${JSON.stringify(f.value)}${o?" (detected in strict mode)":""}`);if(a.add(f.value),!s&&e(n,f.value)&&(s=f.children,o===!1))return!1}else if(m.displayName===w.displayName){if(i)throw new Error(`Switch can only have one Default child at index ${u}`);if(i=!0,c=d.props.children,!o&&s)return!1}else throw new Error(`Switch Children only accepts 'Case' or 'Default' elements, found: ${String(m.displayName||m.name||m)} at index ${u}`)}),r.createElement(r.Fragment,null,s??c)};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 h=({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};h.displayName="If",h.Then=N,h.ElseIf=F,h.Else=v,h.createTyped=function(){return{If:h,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})=>p(()=>(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=p(()=>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))},q=t=>{const{children:n,h:e,w:l,size:o,height:a,width:s,className:c}=t;return r.createElement("div",{style:{width:o||l||s,height:o||e||a,flexShrink:0},className:c},n)},G=({let:t,props:n,children:e,fallback:l})=>{const o=p(()=>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 K=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(R(n)){const o=n;let a=o?.props?.className;return o?.type?.displayName===b.displayName&&K(a)&&(a=M(...Object.values(a))),W(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 Q=t=>{const{index:n=0,options:e,next:l,render:o}=t;H(()=>{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),c=()=>{s(i=>e.length?l?l(i,e):(i+1)%e.length:i)};return o(e[a],c)};function U(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=p(()=>{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=p(()=>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),[c,i]=x(n),d=s?a[o]:c,u=p(()=>a[l],[a,l]),m=_(f=>{const g=typeof f=="function"?f(d):f;e&&e(g,d)===!1||(s||i(g),u&&u(g))},[s,e,d,u]);return[d,m]}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:ne;function re(t,n){let e=t;const l=[],o=()=>e,a=s=>{const c=e;e=s,l.forEach(i=>i(e)),n&&I(n,e,c).catch(i=>{console.error("Error in external state side effect, Please do it within side effects:",i)})};return{get:o,set:a,use:()=>{const[s,c]=r.useState(e);return r.useEffect(()=>(l.push(c),()=>{const i=l.indexOf(c);i>-1&&l.splice(i,1)}),[]),[s,a]}}}function le(t,n){const e=n||new Date,l=e.getFullYear(),o=e.getMonth()+1,a=e.getDate(),s=e.getHours(),c=e.getMinutes(),i=e.getSeconds(),d=e.getMilliseconds(),u=e.getDay(),m=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],f=["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"],Y=f[u],D=m[u],T=o-1,P=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:P,D:a.toString(),DD:a.toString().padStart(2,"0"),d:u.toString(),dd:D,ddd:D,dddd:Y,H:s.toString(),HH:s.toString().padStart(2,"0"),h:(s%12).toString(),hh:(s%12).toString().padStart(2,"0"),m:c.toString(),mm:c.toString().padStart(2,"0"),s:i.toString(),ss:i.toString().padStart(2,"0"),SSS:d.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{U as ArrayRender,ae as Counter,X as DateRender,Z as False,h as If,L as Pipe,G as Scope,q as SizeBox,b as Styles,y as Switch,Q 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.8",
3
+ "version": "1.2.10",
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",
package/src/index.ts CHANGED
@@ -1,9 +1,7 @@
1
- export * from './components/ProcessControl'
2
- export * from './components/Sundry'
3
- export * from './components/Struct'
1
+ export * from "./components/ProcessControl";
2
+ export * from "./components/Sundry";
3
+ export * from "./components/Struct";
4
4
 
5
- export * from './hooks'
5
+ export * from "./hooks";
6
6
 
7
- export * from './utils/sundry'
8
- export * from './utils/cx'
9
- export * from './utils/reactUtils'
7
+ export * from "./utils";
@@ -0,0 +1,114 @@
1
+ import React from "react";
2
+ import { safePromiseTry } from "./promise";
3
+
4
+ /**
5
+ * @en Callback function type for state change listeners
6
+ * @zh 状态变更监听器的回调函数类型
7
+ * @template T The type of the state / 状态的类型
8
+ */
9
+ export type CreateStateListener<T> = (state: T) => void;
10
+
11
+ /**
12
+ * @zh 如果需要在变更状态时执行副作用,可以传入函数,对于异步函数,会在更改状态后执行,不会阻塞状态更新, 尽可能在外部使用useEffect处理异步副作用
13
+ * @en If you need to perform side effects when changing the state, you can pass a function. For asynchronous functions, it will be executed after the state changes without blocking the state update, so it's best to use useEffect for handling asynchronous side effects.
14
+ * @template T The type of the state / 状态的类型
15
+ * @param newState The new state value / 新的状态值
16
+ * @param prevState The previous state value / 之前的状态值
17
+ */
18
+ export type ExternalSideEffect<T> = (
19
+ newState: T,
20
+ prevState: T
21
+ ) => void | Promise<void>;
22
+
23
+ /**
24
+ * @en External state management interface
25
+ * @zh 外部状态管理接口
26
+ * @template T The type of the state / 状态的类型
27
+ */
28
+ export interface ExternalState<T> {
29
+ /**
30
+ * @en Get the current state value
31
+ * @zh 获取当前状态值
32
+ * @returns The current state value / 当前状态值
33
+ */
34
+ get: () => T;
35
+
36
+ /**
37
+ * @en Set a new state value
38
+ * @zh 设置新的状态值
39
+ * @param newState The new state value / 新的状态值
40
+ */
41
+ set: (newState: T) => void;
42
+
43
+ /**
44
+ * @en React Hook for using external state in components
45
+ * @zh 在组件中使用外部状态的 React Hook
46
+ * @returns Array containing current state and update function, similar to useState / 包含当前状态和更新函数的数组,类似于 useState
47
+ */
48
+ use: () => [T, (newState: T) => void];
49
+ }
50
+
51
+ /**
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * // Create an app-level theme state
56
+ * const themeState = createExternalState('light');
57
+ *
58
+ * // Get or modify state outside components
59
+ * console.log(themeState.get()); // 'light'
60
+ * themeState.set('dark');
61
+ *
62
+ * // Use state in components
63
+ * function ThemeConsumer() {
64
+ * const [theme, setTheme] = themeState.use();
65
+ *
66
+ * return (
67
+ * <div className={theme}>
68
+ * <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
69
+ * Toggle theme / 切换主题
70
+ * </button>
71
+ * </div>
72
+ * );
73
+ * }
74
+ * ```
75
+ */
76
+ export function createExternalState<T>(
77
+ initialState: T,
78
+ sideEffect?: ExternalSideEffect<T>
79
+ ): ExternalState<T> {
80
+ let state: T = initialState;
81
+ const listeners: CreateStateListener<T>[] = [];
82
+
83
+ const get = () => state;
84
+
85
+ const set = (newState: T) => {
86
+ const prevState = state;
87
+ state = newState;
88
+
89
+ listeners.forEach((listener) => listener(state));
90
+ if (sideEffect) {
91
+ safePromiseTry(sideEffect, state, prevState).catch((error) => {
92
+ console.error("Error in external state side effect, Please do it within side effects:", error);
93
+ });
94
+ }
95
+ };
96
+
97
+ const use = () => {
98
+ const [localState, setLocalState] = React.useState(state);
99
+
100
+ React.useEffect(() => {
101
+ listeners.push(setLocalState);
102
+ return () => {
103
+ const index = listeners.indexOf(setLocalState);
104
+ if (index > -1) {
105
+ listeners.splice(index, 1);
106
+ }
107
+ };
108
+ }, []);
109
+
110
+ return [localState, set] as [T, (newState: T) => void];
111
+ };
112
+
113
+ return { get, set, use };
114
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./createExternalState";
2
+ export * from "./cx";
3
+ export * from "./reactUtils";
4
+ export * from "./sundry";
5
+ export * from "./promise";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Takes a callback of any kind (returns or throws, synchronously or asynchronously) and wraps its result
3
+ * in a Promise.
4
+ *
5
+ * @param callbackFn A function that is called synchronously. It can do anything: either return
6
+ * a value, throw an error, or return a promise.
7
+ * @param args Additional arguments, that will be passed to the callback.
8
+ *
9
+ * @returns A Promise that is:
10
+ * - Already fulfilled, if the callback synchronously returns a value.
11
+ * - Already rejected, if the callback synchronously throws an error.
12
+ * - Asynchronously fulfilled or rejected, if the callback returns a promise.
13
+ */
14
+ function promiseTry<T, U extends unknown[]>(
15
+ callbackFn: (...args: U) => T | PromiseLike<T>,
16
+ ...args: U
17
+ ): Promise<Awaited<T>> {
18
+ try {
19
+ const result = callbackFn(...args); // Call the callback function
20
+ // Check if the result is a PromiseLike (Promise)
21
+ if (result instanceof Promise) {
22
+ return result; // If it's a promise, return it directly
23
+ }
24
+ // If the result is not a promise, resolve it with Promise.resolve
25
+ return Promise.resolve(result as Awaited<T>);
26
+ } catch (error) {
27
+ // If the callback throws an error, reject the promise
28
+ return Promise.reject(error);
29
+ }
30
+ }
31
+
32
+ export const safePromiseTry = (() => {
33
+ if (typeof Promise.try === "function") {
34
+ return Promise.try;
35
+ } else {
36
+ return promiseTry;
37
+ }
38
+ })();