@gateweb/react-utils 1.17.0 → 2.0.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.
@@ -26,6 +26,8 @@ const createQueryStore = ({ query, handleChangeQuery })=>createStore()((set)=>({
26
26
  const QueryContext = /*#__PURE__*/ createContext(null);
27
27
  /**
28
28
  * Provider to provide the store to the context
29
+ *
30
+ * @deprecated use `StoreProvider` from `core/store` instead
29
31
  */ const QueryProvider = ({ children, query, handleChangeQuery })=>{
30
32
  const storeRef = useRef(null);
31
33
  if (!storeRef.current) {
@@ -57,6 +59,8 @@ const QueryContext = /*#__PURE__*/ createContext(null);
57
59
  * const result2 = useQueryContext<MyObject>()(q => q.changeQuery);
58
60
  * const result3 = useQueryContext<MyObject>()(q => q.query);
59
61
  * ```
62
+ *
63
+ * @deprecated use `useStoreContext` from `core/store` instead
60
64
  */ const useQueryContext = ()=>{
61
65
  const store = useContext(QueryContext);
62
66
  if (!store) throw new Error('Missing QueryContext.Provider in the tree');
@@ -0,0 +1,118 @@
1
+ 'use client';
2
+ import React, { useContext, createContext, useRef } from 'react';
3
+ import { createStore } from 'zustand';
4
+ import { useStoreWithEqualityFn } from 'zustand/traditional';
5
+
6
+ const isObject = (value)=>value !== null && typeof value === 'object';
7
+ /**
8
+ * merge two objects deeply
9
+ *
10
+ * @param target - the target object
11
+ * @param source - the source object
12
+ *
13
+ * @example
14
+ *
15
+ * const obja = { a: { a1: { a11: 'value 1', a12: 'value 2' }, a2: 'value 3' }, b: 'value 4' };
16
+ * const objb = { a: { a1: { a13: 'value 5', a14: 'value 6' }, a3: 'value 7' }};
17
+ *
18
+ * const mergeResult = deepMerge(obja, objb); // { a: { a1: { a11: 'value 1', a12: 'value 2', a13: 'value 5', a14: 'value 6' }, a2: 'value 3', a3: 'value 7' }, b: 'value 4' }
19
+ *
20
+ */ const deepMerge = (target, source)=>Object.entries(source).reduce((acc, [key, value])=>{
21
+ if (isObject(value)) {
22
+ acc[key] = key in target && isObject(target[key]) ? deepMerge(target[key], value) : value;
23
+ } else {
24
+ acc[key] = value;
25
+ }
26
+ return acc;
27
+ }, {
28
+ ...target
29
+ });
30
+
31
+ /**
32
+ * create a zustand store with changeStore method
33
+ */ const createZustandStore = ({ store, handleChangeStore, defaultMerge })=>createStore()((set)=>({
34
+ store,
35
+ changeStore: (updater, options)=>{
36
+ set((pre)=>{
37
+ const patch = typeof updater === 'function' ? updater(pre.store) : updater;
38
+ const newStore = handleChangeStore ? handleChangeStore(pre.store, patch) : patch;
39
+ const strategy = options?.merge ?? defaultMerge;
40
+ if (strategy === 'shallow') {
41
+ return {
42
+ store: {
43
+ ...pre.store,
44
+ ...newStore
45
+ }
46
+ };
47
+ }
48
+ return {
49
+ store: deepMerge(pre.store, newStore)
50
+ };
51
+ });
52
+ }
53
+ }));
54
+ /**
55
+ * create a store context with provider and hooks
56
+ *
57
+ * @example
58
+ *
59
+ * ```tsx
60
+ * // create a store context
61
+ * const { StoreProvider, useStore, useStoreApi } = createStoreContext<MyObject>();
62
+ *
63
+ * // use the StoreProvider to provide the store to the context
64
+ * <StoreProvider store={{ key: 'value' }}>
65
+ * <App />
66
+ * </StoreProvider>
67
+ * ```
68
+ */ const createStoreContext = ()=>{
69
+ const StoreContext = /*#__PURE__*/ createContext(null);
70
+ /**
71
+ * Provider to provide the store to the context
72
+ *
73
+ * @example
74
+ *
75
+ * ```tsx
76
+ * // use the StoreProvider to provide the store to the context
77
+ * const { StoreProvider } = createStoreContext<MyObject>();
78
+ *
79
+ * <StoreProvider store={{ key: 'value' }}>
80
+ * <App />
81
+ * </StoreProvider>
82
+ * ```
83
+ */ const StoreProvider = ({ children, store = {}, handleChangeStore, defaultMerge = 'shallow' })=>{
84
+ const storeRef = useRef(null);
85
+ if (!storeRef.current) {
86
+ storeRef.current = createZustandStore({
87
+ store,
88
+ handleChangeStore,
89
+ defaultMerge
90
+ });
91
+ }
92
+ return /*#__PURE__*/ React.createElement(StoreContext.Provider, {
93
+ value: storeRef.current
94
+ }, children);
95
+ };
96
+ /**
97
+ * a hook to get the store from the context
98
+ *
99
+ * @example
100
+ * ```tsx
101
+ * const { useStore } = createStoreContext<MyObject>();
102
+ * const value = useStore(s => s.store);
103
+ * ```
104
+ *
105
+ * @param selector - the selector to get the state from the store
106
+ * @param equalityFn - the equality function to compare the previous and next state
107
+ */ const useStore = (selector, equalityFn)=>{
108
+ const store = useContext(StoreContext);
109
+ if (!store) throw new Error('Missing StoreContext.Provider in the tree');
110
+ return useStoreWithEqualityFn(store, selector, equalityFn);
111
+ };
112
+ return {
113
+ StoreProvider,
114
+ useStore
115
+ };
116
+ };
117
+
118
+ export { createStoreContext as c };
@@ -0,0 +1,135 @@
1
+ 'use client';
2
+ /* eslint-disable no-bitwise */ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
3
+ /**
4
+ * 將位元組陣列編碼為 base64 字串
5
+ *
6
+ * @param bytes 要編碼的位元組陣列
7
+ * @returns base64 編碼的字串
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const bytes = new TextEncoder().encode('Hello World');
12
+ * const encoded = encodeBase64(bytes);
13
+ * console.log(encoded); // 'SGVsbG8gV29ybGQ='
14
+ * ```
15
+ */ const encodeBase64 = (bytes)=>{
16
+ let out = '';
17
+ for(let i = 0; i < bytes.length; i += 3){
18
+ const n = bytes[i] << 16 | (bytes[i + 1] || 0) << 8 | (bytes[i + 2] || 0);
19
+ out += chars[n >> 18 & 63] + chars[n >> 12 & 63] + (i + 1 < bytes.length ? chars[n >> 6 & 63] : '=') + (i + 2 < bytes.length ? chars[n & 63] : '=');
20
+ }
21
+ return out;
22
+ };
23
+ /**
24
+ * 將 base64 字串解碼為位元組陣列
25
+ *
26
+ * @param base64 要解碼的 base64 字串(支援 base64url 格式)
27
+ * @returns 解碼後的位元組陣列
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const decoded = decodeBase64('SGVsbG8gV29ybGQ=');
32
+ * const text = new TextDecoder().decode(decoded);
33
+ * console.log(text); // 'Hello World'
34
+ * ```
35
+ */ const decodeBase64 = (base64)=>{
36
+ let out = base64.replace(/-/g, '+').replace(/_/g, '/'); // 支援 base64url
37
+ while(out.length % 4)out += '='; // 自動補 "="
38
+ const buffer = [];
39
+ for(let i = 0; i < out.length; i += 4){
40
+ const n = chars.indexOf(out[i]) << 18 | chars.indexOf(out[i + 1]) << 12 | (chars.indexOf(out[i + 2]) & 63) << 6 | chars.indexOf(out[i + 3]) & 63;
41
+ buffer.push(n >> 16 & 255);
42
+ if (out[i + 2] !== '=') buffer.push(n >> 8 & 255);
43
+ if (out[i + 3] !== '=') buffer.push(n & 255);
44
+ }
45
+ return new Uint8Array(buffer);
46
+ };
47
+ /**
48
+ * 將 JSON 資料編碼為 base64 字串
49
+ *
50
+ * @param data 要編碼的 JSON 可序列化資料
51
+ * @returns base64 編碼的字串
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const data = { name: 'John', age: 30 };
56
+ * const encoded = encodeJson(data);
57
+ * console.log(encoded); // 'eyJuYW1lIjoiSm9obiIsImFnZSI6MzB9'
58
+ *
59
+ * const array = [1, 2, 3];
60
+ * const encodedArray = encodeJson(array);
61
+ * ```
62
+ */ const encodeJson = (data)=>{
63
+ const json = JSON.stringify(data);
64
+ return encodeBase64(new TextEncoder().encode(json));
65
+ };
66
+ /**
67
+ * 將 base64 字串解碼為 JSON 資料
68
+ *
69
+ * @param b64 要解碼的 base64 字串
70
+ * @returns 解碼後的資料
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * const encoded = 'eyJuYW1lIjoiSm9obiIsImFnZSI6MzB9';
75
+ * const decoded = decodeJson<{ name: string; age: number }>(encoded);
76
+ * console.log(decoded); // { name: 'John', age: 30 }
77
+ *
78
+ * const decodedArray = decodeJson<number[]>(encodedArray);
79
+ * ```
80
+ */ const decodeJson = (b64)=>{
81
+ const json = new TextDecoder().decode(decodeBase64(b64));
82
+ return JSON.parse(json);
83
+ };
84
+
85
+ /**
86
+ * 從 localStorage 取得資料,支援槽狀取值(Json 物件)
87
+ *
88
+ * @param key 鍵值
89
+ * @param deCode 是否解碼
90
+ * @returns 取得的資料
91
+ * @example
92
+ * const data = getLocalStorage('key');
93
+ *
94
+ * const data = getLocalStorage('key.subKey');
95
+ */ const getLocalStorage = (key, deCode = true)=>{
96
+ const keys = key.split('.');
97
+ const storage = localStorage.getItem(keys[0]);
98
+ if (!storage) return undefined;
99
+ let currentObject;
100
+ try {
101
+ if (deCode) {
102
+ currentObject = decodeJson(storage);
103
+ } else {
104
+ currentObject = JSON.parse(storage);
105
+ }
106
+ } catch (_error) {
107
+ return undefined;
108
+ }
109
+ // let currentObject = JSON.parse(storage);
110
+ if (keys.length === 1) {
111
+ return currentObject;
112
+ }
113
+ keys.shift();
114
+ while(keys.length > 0){
115
+ const currentKey = keys.shift();
116
+ if (!currentKey) break;
117
+ currentObject = currentObject[currentKey];
118
+ if (!currentObject) break;
119
+ }
120
+ return currentObject;
121
+ };
122
+ /**
123
+ * 將資料(Json 物件)存入 localStorage
124
+ * @param key 鍵值
125
+ * @param value 可序列化的資料
126
+ * @param enCode 是否編碼
127
+ */ const setLocalStorage = (key, value, enCode = true)=>{
128
+ if (enCode) {
129
+ localStorage.setItem(key, encodeJson(value));
130
+ } else {
131
+ localStorage.setItem(key, JSON.stringify(value));
132
+ }
133
+ };
134
+
135
+ export { getLocalStorage as g, setLocalStorage as s };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gateweb/react-utils",
3
- "version": "1.17.0",
3
+ "version": "2.0.0",
4
4
  "description": "React Utils for GateWeb",
5
5
  "homepage": "https://github.com/GatewebSolutions/react-utils",
6
6
  "files": [
@@ -20,6 +20,16 @@
20
20
  "default": "./dist/cjs/index.js"
21
21
  }
22
22
  },
23
+ "./client": {
24
+ "import": {
25
+ "types": "./dist/es/client.d.mts",
26
+ "default": "./dist/es/client.mjs"
27
+ },
28
+ "require": {
29
+ "types": "./dist/cjs/client.d.ts",
30
+ "default": "./dist/cjs/client.js"
31
+ }
32
+ },
23
33
  "./types": {
24
34
  "import": {
25
35
  "types": "./dist/es/types.d.mts",