vanilla-jui 1.6.6 → 1.7.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jui",
3
- "version": "1.6.6",
3
+ "version": "1.7.1",
4
4
  "private": false,
5
5
  "description": "A lightweight, reactive, and fully customizable vanilla JS UI library.",
6
6
  "keywords": [
@@ -97,13 +97,13 @@
97
97
  color: var(--tone-text);
98
98
  } */
99
99
 
100
- .j-menu .menu .menu-item .sub-menu .menu-item:hover a,
100
+ /* .j-menu .menu .menu-item .sub-menu .menu-item:hover a,
101
101
  .j-menu .menu .menu-item a:hover,
102
- .j-menu .menu .menu-item.menu-item-has-children:hover > a {
102
+ .j-menu .menu .menu-item.menu-item-has-children:hover>a {
103
103
  text-decoration: none;
104
104
  background: var(--tone-soft);
105
105
  color: var(--tone-text);
106
- }
106
+ } */
107
107
 
108
108
  .j-menu .menu > .menu-item.current-menu-item > a,
109
109
  .j-menu .menu > .menu-item.current-menu-ancestor > a {
@@ -127,14 +127,20 @@
127
127
  color: var(--tone-fg);
128
128
  }
129
129
 
130
+ .j-menu .menu .menu-item .sub-menu .menu-item:hover a,
131
+ .j-menu .menu .menu-item a:hover,
132
+ .j-menu .menu .menu-item.menu-item-has-children:hover > a,
130
133
  .j-menu
131
134
  .menu-item.menu-item-has-children:hover
132
135
  > .sub-menu
133
136
  > .menu-item.current-menu-item
134
137
  a {
135
- color: var(--tone-fg);
136
- background-color: var(--tone-solid);
137
- outline: 1px solid var(--tone-solid);
138
+ text-decoration: none;
139
+ background: var(--tone-soft);
140
+ color: var(--tone-text);
141
+ /* color: var(--tone-fg); */
142
+ /* background-color: var(--tone-solid); */
143
+ /* outline: 1px solid var(--tone-solid); */
138
144
  }
139
145
 
140
146
  .j-menu .menu .menu-item:not(:has(a)):hover,
@@ -55,3 +55,194 @@ export function randomId(length: number = 8): string {
55
55
 
56
56
  return base64.substring(0, length).replace(/\+/g, '-').replace(/\//g, '_');
57
57
  }
58
+
59
+ /**
60
+ * 查询参数值类型
61
+ */
62
+ type QueryParamValue =
63
+ | string
64
+ | number
65
+ | boolean
66
+ | null
67
+ | undefined
68
+ | QueryParamValue[]
69
+ | { [key: string]: QueryParamValue };
70
+
71
+ /**
72
+ * 查询参数对象类型
73
+ */
74
+ type QueryParams = Record<string, QueryParamValue>;
75
+
76
+ const SHA256_K = new Uint32Array([
77
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
78
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
79
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
80
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
81
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
82
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
83
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
84
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
85
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
86
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
87
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
88
+ ]);
89
+
90
+ const SHA256_INITIAL = new Uint32Array([
91
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c,
92
+ 0x1f83d9ab, 0x5be0cd19,
93
+ ]);
94
+
95
+ function rotateRight(value: number, bits: number): number {
96
+ return (value >>> bits) | (value << (32 - bits));
97
+ }
98
+
99
+ function sha256Fallback(input: Uint8Array): Uint8Array {
100
+ const message = new Uint8Array(Math.ceil((input.length + 9) / 64) * 64);
101
+ message.set(input);
102
+ message[input.length] = 0x80;
103
+
104
+ const bitLength = BigInt(input.length) * 8n;
105
+ for (let i = 0; i < 8; i += 1) {
106
+ message[message.length - 1 - i] = Number(
107
+ (bitLength >> BigInt(i * 8)) & 0xffn
108
+ );
109
+ }
110
+
111
+ const hash = new Uint32Array(SHA256_INITIAL);
112
+ const words = new Uint32Array(64);
113
+
114
+ for (let offset = 0; offset < message.length; offset += 64) {
115
+ for (let i = 0; i < 16; i += 1) {
116
+ const index = offset + i * 4;
117
+ words[i] =
118
+ ((message[index] << 24) |
119
+ (message[index + 1] << 16) |
120
+ (message[index + 2] << 8) |
121
+ message[index + 3]) >>>
122
+ 0;
123
+ }
124
+
125
+ for (let i = 16; i < 64; i += 1) {
126
+ const s0 =
127
+ rotateRight(words[i - 15], 7) ^
128
+ rotateRight(words[i - 15], 18) ^
129
+ (words[i - 15] >>> 3);
130
+ const s1 =
131
+ rotateRight(words[i - 2], 17) ^
132
+ rotateRight(words[i - 2], 19) ^
133
+ (words[i - 2] >>> 10);
134
+ words[i] = (words[i - 16] + s0 + words[i - 7] + s1) >>> 0;
135
+ }
136
+
137
+ let a = hash[0];
138
+ let b = hash[1];
139
+ let c = hash[2];
140
+ let d = hash[3];
141
+ let e = hash[4];
142
+ let f = hash[5];
143
+ let g = hash[6];
144
+ let h = hash[7];
145
+
146
+ for (let i = 0; i < 64; i += 1) {
147
+ const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
148
+ const ch = (e & f) ^ (~e & g);
149
+ const temp1 = (h + s1 + ch + SHA256_K[i] + words[i]) >>> 0;
150
+ const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
151
+ const maj = (a & b) ^ (a & c) ^ (b & c);
152
+ const temp2 = (s0 + maj) >>> 0;
153
+
154
+ h = g;
155
+ g = f;
156
+ f = e;
157
+ e = (d + temp1) >>> 0;
158
+ d = c;
159
+ c = b;
160
+ b = a;
161
+ a = (temp1 + temp2) >>> 0;
162
+ }
163
+
164
+ hash[0] = (hash[0] + a) >>> 0;
165
+ hash[1] = (hash[1] + b) >>> 0;
166
+ hash[2] = (hash[2] + c) >>> 0;
167
+ hash[3] = (hash[3] + d) >>> 0;
168
+ hash[4] = (hash[4] + e) >>> 0;
169
+ hash[5] = (hash[5] + f) >>> 0;
170
+ hash[6] = (hash[6] + g) >>> 0;
171
+ hash[7] = (hash[7] + h) >>> 0;
172
+ }
173
+
174
+ const output = new Uint8Array(32);
175
+ for (let i = 0; i < hash.length; i += 1) {
176
+ output[i * 4] = hash[i] >>> 24;
177
+ output[i * 4 + 1] = hash[i] >>> 16;
178
+ output[i * 4 + 2] = hash[i] >>> 8;
179
+ output[i * 4 + 3] = hash[i];
180
+ }
181
+ return output;
182
+ }
183
+
184
+ async function sha256(input: Uint8Array): Promise<Uint8Array> {
185
+ const subtle = globalThis.crypto?.subtle;
186
+ if (subtle && typeof subtle.digest === 'function') {
187
+ const source = new ArrayBuffer(input.byteLength);
188
+ new Uint8Array(source).set(input);
189
+ return new Uint8Array(await subtle.digest('SHA-256', source));
190
+ }
191
+ return sha256Fallback(input);
192
+ }
193
+
194
+ /**
195
+ * 对查询参数进行 SHA-256 哈希编码,生成用于缓存的 key
196
+ *
197
+ * @param params - 查询参数对象
198
+ * @param bytes - 取哈希前 N 字节,默认为 8,范围 1-32
199
+ * @returns 36 进制哈希字符串
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * const key = await hashQueryParams({ page: 1, sort: 'desc' });
204
+ * // 返回: "2k7x9f3m4n5p"
205
+ * ```
206
+ */
207
+ export async function hashQueryParams(
208
+ params: QueryParams,
209
+ bytes: number = 8
210
+ ): Promise<string> {
211
+ // 1. 参数验证
212
+ if (typeof params !== 'object' || params === null) {
213
+ throw new TypeError('params must be a non-null object');
214
+ }
215
+
216
+ if (bytes < 1 || bytes > 32) {
217
+ throw new RangeError('bytes must be between 1 and 32');
218
+ }
219
+
220
+ // 2. 排序并规范化参数
221
+ const normalized: QueryParams = {};
222
+ const sortedKeys = Object.keys(params).sort();
223
+
224
+ for (const key of sortedKeys) {
225
+ const value = params[key];
226
+ // 字符串 trim 处理
227
+ normalized[key] = typeof value === 'string' ? value.trim() : value;
228
+ }
229
+
230
+ // 3. 序列化
231
+ const str = JSON.stringify(normalized);
232
+ const buffer = new TextEncoder().encode(str);
233
+
234
+ // 4. SHA-256 哈希
235
+ const hashBuffer = await sha256(buffer);
236
+
237
+ // 5. 取前 N 字节(防越界)
238
+ const safeBytes = Math.min(bytes, hashBuffer.byteLength);
239
+ const slice = hashBuffer.subarray(0, safeBytes);
240
+
241
+ // 6. 转为 BigInt 再转 36 进制
242
+ let num = 0n;
243
+ for (const byte of slice) {
244
+ num = (num << 8n) | BigInt(byte);
245
+ }
246
+
247
+ return num.toString(36);
248
+ }