vanilla-jui 1.5.7 → 1.5.9
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/dist/index.d.ts +39 -23
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/menu.ts +78 -25
- package/src/css/content.css +2 -1
- package/src/css/themes/default/toast.css +1 -8
- package/src/css/toast.css +10 -1
- package/src/locales/index.ts +1 -0
- package/src/primitives/theme.ts +1 -2
- package/src/primitives/toast.ts +173 -68
- package/src/utilities/browser.ts +4 -4
package/package.json
CHANGED
package/src/components/menu.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
For,
|
|
3
|
+
type MaybeAccessor,
|
|
4
|
+
access,
|
|
5
|
+
createDeepStore,
|
|
6
|
+
createEffect,
|
|
7
|
+
createMemo,
|
|
8
|
+
flushSync,
|
|
9
|
+
jsx,
|
|
10
|
+
} from 'vanilla-signal';
|
|
2
11
|
import { t } from 'vanilla-signal-i18n';
|
|
3
12
|
|
|
4
13
|
import {
|
|
@@ -18,6 +27,7 @@ import {
|
|
|
18
27
|
|
|
19
28
|
export type MenuType = string | undefined;
|
|
20
29
|
export type MenuItemId = string | number;
|
|
30
|
+
export type MenuItemRenderType = 0 | 1 | 2;
|
|
21
31
|
|
|
22
32
|
export interface MenuClassNames {
|
|
23
33
|
root: string;
|
|
@@ -37,6 +47,7 @@ export type MenuClassNameConfig = Partial<MenuClassNames>;
|
|
|
37
47
|
export interface MenuItem extends Record<string, unknown> {
|
|
38
48
|
id?: MenuItemId;
|
|
39
49
|
title: string | number;
|
|
50
|
+
type?: MenuItemRenderType;
|
|
40
51
|
url?: string;
|
|
41
52
|
target?: string;
|
|
42
53
|
classes?: string | string[];
|
|
@@ -46,7 +57,8 @@ export interface MenuItem extends Record<string, unknown> {
|
|
|
46
57
|
export interface MenuProps extends Record<string, unknown> {
|
|
47
58
|
type?: MenuType;
|
|
48
59
|
id?: string | null;
|
|
49
|
-
|
|
60
|
+
user?: MaybeAccessor<number>;
|
|
61
|
+
data?: MaybeAccessor<MenuItem[]>;
|
|
50
62
|
backText?: string;
|
|
51
63
|
className?: MenuClassNameConfig;
|
|
52
64
|
}
|
|
@@ -54,12 +66,14 @@ export interface MenuProps extends Record<string, unknown> {
|
|
|
54
66
|
interface ResolvedMenuProps extends Record<string, unknown> {
|
|
55
67
|
type?: MenuType;
|
|
56
68
|
id: string;
|
|
57
|
-
|
|
69
|
+
user: MaybeAccessor<number>;
|
|
70
|
+
data: MaybeAccessor<MenuItem[]>;
|
|
58
71
|
backText: string;
|
|
59
72
|
className: MenuClassNames;
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
interface MenuState extends Record<string, unknown> {
|
|
76
|
+
user: number;
|
|
63
77
|
data: MenuItem[];
|
|
64
78
|
activeKeys: string[];
|
|
65
79
|
}
|
|
@@ -80,7 +94,7 @@ const DEFAULT_CLASS_NAMES: MenuClassNames = {
|
|
|
80
94
|
const MENU_ITEM_RULE = {
|
|
81
95
|
type: 'object',
|
|
82
96
|
validate: isMenuItem,
|
|
83
|
-
message: 'expects { title, id?, url?, target?, classes?, children? }.',
|
|
97
|
+
message: 'expects { title, id?, type?, url?, target?, classes?, children? }.',
|
|
84
98
|
};
|
|
85
99
|
|
|
86
100
|
const MENU_DATA_RULE = {
|
|
@@ -90,6 +104,7 @@ const MENU_DATA_RULE = {
|
|
|
90
104
|
|
|
91
105
|
const MENU_PROPS_SCHEMA = {
|
|
92
106
|
type: { default: undefined, types: ['string', 'undefined'] },
|
|
107
|
+
user: { default: 0, types: ['number', 'function'] },
|
|
93
108
|
id: {
|
|
94
109
|
default: null,
|
|
95
110
|
types: ['string', 'null'],
|
|
@@ -104,9 +119,7 @@ const MENU_PROPS_SCHEMA = {
|
|
|
104
119
|
},
|
|
105
120
|
data: {
|
|
106
121
|
default: [],
|
|
107
|
-
|
|
108
|
-
normalize: cloneMenuData,
|
|
109
|
-
items: MENU_ITEM_RULE,
|
|
122
|
+
types: ['array', 'function'],
|
|
110
123
|
},
|
|
111
124
|
backText: { default: t('b', locales), type: 'string' },
|
|
112
125
|
className: {
|
|
@@ -120,6 +133,7 @@ const MENU_PROPS_SCHEMA = {
|
|
|
120
133
|
} satisfies ResolveSchema<MenuProps>;
|
|
121
134
|
|
|
122
135
|
const MENU_STATE_SCHEMA = {
|
|
136
|
+
user: { type: 'number' },
|
|
123
137
|
data: MENU_DATA_RULE,
|
|
124
138
|
activeKeys: { type: 'array', items: 'string' },
|
|
125
139
|
};
|
|
@@ -133,7 +147,8 @@ function normalizeProps(input: MenuProps = {}): ResolvedMenuProps {
|
|
|
133
147
|
|
|
134
148
|
return {
|
|
135
149
|
...props,
|
|
136
|
-
|
|
150
|
+
user: props.user,
|
|
151
|
+
data: props.data,
|
|
137
152
|
className: { ...props.className },
|
|
138
153
|
};
|
|
139
154
|
}
|
|
@@ -165,6 +180,10 @@ function isMenuItem(value: unknown): value is MenuItem {
|
|
|
165
180
|
(item.id == null ||
|
|
166
181
|
typeof item.id === 'string' ||
|
|
167
182
|
typeof item.id === 'number') &&
|
|
183
|
+
(item.type == null ||
|
|
184
|
+
item.type === 0 ||
|
|
185
|
+
item.type === 1 ||
|
|
186
|
+
item.type === 2) &&
|
|
168
187
|
(item.url == null || typeof item.url === 'string') &&
|
|
169
188
|
(item.target == null || typeof item.target === 'string') &&
|
|
170
189
|
(item.classes == null ||
|
|
@@ -181,6 +200,7 @@ function cloneMenuData(data: unknown): MenuItem[] {
|
|
|
181
200
|
return data.map((item) => {
|
|
182
201
|
validateParam('item', item, MENU_ITEM_RULE, 'Menu.data');
|
|
183
202
|
const nextItem = { ...(item as MenuItem) };
|
|
203
|
+
nextItem.type = nextItem.type ?? 0;
|
|
184
204
|
if (Array.isArray(nextItem.classes)) {
|
|
185
205
|
nextItem.classes = nextItem.classes.slice();
|
|
186
206
|
}
|
|
@@ -191,6 +211,20 @@ function cloneMenuData(data: unknown): MenuItem[] {
|
|
|
191
211
|
});
|
|
192
212
|
}
|
|
193
213
|
|
|
214
|
+
function resolveMenuUser(user: MaybeAccessor<number> | undefined): number {
|
|
215
|
+
const value = access(user ?? 0);
|
|
216
|
+
validateParam('user', value, 'number', 'Menu.props');
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function resolveMenuData(
|
|
221
|
+
data: MaybeAccessor<MenuItem[]> | undefined
|
|
222
|
+
): MenuItem[] {
|
|
223
|
+
const value = access(data ?? []);
|
|
224
|
+
validateParam('data', value, MENU_DATA_RULE, 'Menu.props');
|
|
225
|
+
return cloneMenuData(value);
|
|
226
|
+
}
|
|
227
|
+
|
|
194
228
|
export type Menu = FunctionalComponent<
|
|
195
229
|
ResolvedMenuProps,
|
|
196
230
|
MenuState,
|
|
@@ -200,7 +234,8 @@ export type Menu = FunctionalComponent<
|
|
|
200
234
|
export function createMenu(input: MenuProps = {}): Menu {
|
|
201
235
|
const props = normalizeProps(input);
|
|
202
236
|
const state = createDeepStore({
|
|
203
|
-
|
|
237
|
+
user: resolveMenuUser(props.user),
|
|
238
|
+
data: resolveMenuData(props.data),
|
|
204
239
|
activeKeys: [],
|
|
205
240
|
}) as MenuState;
|
|
206
241
|
const generatedKeys = new WeakMap<object, string>();
|
|
@@ -213,6 +248,16 @@ export function createMenu(input: MenuProps = {}): Menu {
|
|
|
213
248
|
return generatedKeys.get(item) as string;
|
|
214
249
|
};
|
|
215
250
|
const isActive = (key: string): boolean => state.activeKeys.includes(key);
|
|
251
|
+
const isLoggedIn = (): boolean => state.user > 0;
|
|
252
|
+
const shouldRenderItem = (item: MenuItem): boolean => {
|
|
253
|
+
if (item.type === 1) return isLoggedIn();
|
|
254
|
+
if (item.type === 2) return !isLoggedIn();
|
|
255
|
+
return true;
|
|
256
|
+
};
|
|
257
|
+
const visibleItems = (items: readonly MenuItem[] | undefined): MenuItem[] => {
|
|
258
|
+
if (!Array.isArray(items)) return [];
|
|
259
|
+
return items.filter(shouldRenderItem);
|
|
260
|
+
};
|
|
216
261
|
const open = (key: string): void => {
|
|
217
262
|
flushSync(() => {
|
|
218
263
|
state.activeKeys =
|
|
@@ -282,12 +327,16 @@ export function createMenu(input: MenuProps = {}): Menu {
|
|
|
282
327
|
key: (item: MenuItem) => itemKey(item),
|
|
283
328
|
children: (itemAccessor: () => MenuItem) => {
|
|
284
329
|
const key = itemKey(itemAccessor());
|
|
330
|
+
const childrenAccessor = createMemo(() =>
|
|
331
|
+
visibleItems(itemAccessor().children)
|
|
332
|
+
);
|
|
333
|
+
const hasChildren = (): boolean => childrenAccessor().length > 0;
|
|
285
334
|
return jsx('li', {
|
|
286
335
|
className: () => {
|
|
287
336
|
const item = itemAccessor();
|
|
288
337
|
return joinClasses(
|
|
289
338
|
props.className.item,
|
|
290
|
-
|
|
339
|
+
hasChildren() ? props.className.hasChildren : '',
|
|
291
340
|
...normalizeItemClasses(item.classes),
|
|
292
341
|
isActive(key) ? props.className.active : ''
|
|
293
342
|
);
|
|
@@ -298,13 +347,12 @@ export function createMenu(input: MenuProps = {}): Menu {
|
|
|
298
347
|
: itemId(itemAccessor().id),
|
|
299
348
|
'data-menu-item': () =>
|
|
300
349
|
itemAccessor().id == null ? '' : String(itemAccessor().id),
|
|
301
|
-
'data-menu-has-children': () =>
|
|
302
|
-
itemAccessor().children?.length ? '' : null,
|
|
350
|
+
'data-menu-has-children': () => (hasChildren() ? '' : null),
|
|
303
351
|
onClick: (event: Event) => {
|
|
304
352
|
if (
|
|
305
353
|
props.type === 'mobile' &&
|
|
306
354
|
event.target === event.currentTarget &&
|
|
307
|
-
|
|
355
|
+
hasChildren()
|
|
308
356
|
) {
|
|
309
357
|
open(key);
|
|
310
358
|
}
|
|
@@ -312,9 +360,9 @@ export function createMenu(input: MenuProps = {}): Menu {
|
|
|
312
360
|
children: [
|
|
313
361
|
() => {
|
|
314
362
|
const item = itemAccessor();
|
|
315
|
-
const
|
|
363
|
+
const itemHasChildren = hasChildren();
|
|
316
364
|
const url = itemUrl(item.url);
|
|
317
|
-
if (!
|
|
365
|
+
if (!itemHasChildren && !url) {
|
|
318
366
|
return jsx('span', {
|
|
319
367
|
className: props.className.link,
|
|
320
368
|
'data-menu-link': '',
|
|
@@ -327,7 +375,7 @@ export function createMenu(input: MenuProps = {}): Menu {
|
|
|
327
375
|
target: item.target,
|
|
328
376
|
'data-menu-link': '',
|
|
329
377
|
onClick: (event: Event) => {
|
|
330
|
-
if (!
|
|
378
|
+
if (!itemHasChildren) return;
|
|
331
379
|
if (props.type === 'mobile' || props.type === 'bottom') {
|
|
332
380
|
event.preventDefault();
|
|
333
381
|
if (props.type === 'bottom') toggle(key);
|
|
@@ -338,13 +386,8 @@ export function createMenu(input: MenuProps = {}): Menu {
|
|
|
338
386
|
});
|
|
339
387
|
},
|
|
340
388
|
() => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return listView(
|
|
344
|
-
() => itemAccessor().children || [],
|
|
345
|
-
false,
|
|
346
|
-
key
|
|
347
|
-
);
|
|
389
|
+
if (!hasChildren()) return null;
|
|
390
|
+
return listView(childrenAccessor, false, key);
|
|
348
391
|
},
|
|
349
392
|
],
|
|
350
393
|
});
|
|
@@ -359,6 +402,9 @@ export function createMenu(input: MenuProps = {}): Menu {
|
|
|
359
402
|
state,
|
|
360
403
|
normalizeStatePatch(patch) {
|
|
361
404
|
const next = { ...patch };
|
|
405
|
+
if (Object.hasOwn(next, 'user')) {
|
|
406
|
+
next.user = resolveMenuUser(next.user as MaybeAccessor<number>);
|
|
407
|
+
}
|
|
362
408
|
if (Object.hasOwn(next, 'data')) next.data = cloneMenuData(next.data);
|
|
363
409
|
return next;
|
|
364
410
|
},
|
|
@@ -377,12 +423,19 @@ export function createMenu(input: MenuProps = {}): Menu {
|
|
|
377
423
|
}
|
|
378
424
|
},
|
|
379
425
|
view: () => {
|
|
380
|
-
|
|
426
|
+
const renderedData = createMemo(() => visibleItems(state.data));
|
|
427
|
+
createEffect(() => {
|
|
428
|
+
state.user = resolveMenuUser(props.user);
|
|
429
|
+
state.data = resolveMenuData(props.data);
|
|
430
|
+
});
|
|
431
|
+
createEffect(() =>
|
|
432
|
+
validateParam('data', state.data, MENU_DATA_RULE, 'Menu')
|
|
433
|
+
);
|
|
381
434
|
return jsx('nav', {
|
|
382
435
|
className: props.className.root,
|
|
383
436
|
'data-menu': 'root',
|
|
384
437
|
'data-menu-type': props.type,
|
|
385
|
-
children: listView(
|
|
438
|
+
children: listView(renderedData, true),
|
|
386
439
|
}) as HTMLElement;
|
|
387
440
|
},
|
|
388
441
|
onBuild(context) {
|
package/src/css/content.css
CHANGED
|
@@ -360,6 +360,7 @@
|
|
|
360
360
|
gap: 12px;
|
|
361
361
|
padding: 0.5rem 1rem 0;
|
|
362
362
|
font-size: 0.78rem;
|
|
363
|
+
color: var(--ui-fg-soft);
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
.j-code-editor .code-header .code-dots {
|
|
@@ -594,7 +595,7 @@
|
|
|
594
595
|
}
|
|
595
596
|
|
|
596
597
|
.j-content tbody tr:hover {
|
|
597
|
-
background-color: var(--
|
|
598
|
+
background-color: var(--content-bg-subtle);
|
|
598
599
|
}
|
|
599
600
|
|
|
600
601
|
.j-content tbody tr:last-child td {
|
|
@@ -47,18 +47,11 @@
|
|
|
47
47
|
--toast-border-color: var(--tone-border);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
.j-toast.is-
|
|
50
|
+
.j-toast.is-confirm {
|
|
51
51
|
--toast-gap: calc(var(--space)*4);
|
|
52
52
|
--toast-min-width: 280px;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
.j-toast.is-action .toast-actions {
|
|
56
|
-
width: 100%;
|
|
57
|
-
display: flex;
|
|
58
|
-
justify-content: flex-end;
|
|
59
|
-
gap: var(--space);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
55
|
.j-toast-lite {
|
|
63
56
|
font-size: var(--font-size-md);
|
|
64
57
|
color: var(--ui-bg);
|
package/src/css/toast.css
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
.j-toast .el-icon {
|
|
38
38
|
margin-right: var(--s2);
|
|
39
39
|
width: var(--toast-icon-size);
|
|
40
|
+
position: relative;
|
|
40
41
|
color: inherit;
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -58,9 +59,17 @@
|
|
|
58
59
|
transform: translate(-50%, -50%);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
.j-toast.is-
|
|
62
|
+
.j-toast.is-confirm {
|
|
62
63
|
flex-direction: column;
|
|
63
64
|
align-items: flex-start;
|
|
64
65
|
cursor: default;
|
|
65
66
|
}
|
|
67
|
+
|
|
68
|
+
.j-toast.is-confirm .toast-buttons {
|
|
69
|
+
width: 100%;
|
|
70
|
+
display: flex;
|
|
71
|
+
justify-content: flex-end;
|
|
72
|
+
gap: var(--space);
|
|
73
|
+
position: relative;
|
|
74
|
+
}
|
|
66
75
|
}
|
package/src/locales/index.ts
CHANGED
package/src/primitives/theme.ts
CHANGED
|
@@ -289,7 +289,6 @@ export function createTheme(options: ThemeOptions = {}): ThemeInstance {
|
|
|
289
289
|
children: groups.map((group) =>
|
|
290
290
|
jsx('div', {
|
|
291
291
|
className: className.item,
|
|
292
|
-
'data-palette': group.type,
|
|
293
292
|
'data-theme-group': group.type,
|
|
294
293
|
children: [
|
|
295
294
|
jsx('div', {
|
|
@@ -308,7 +307,7 @@ export function createTheme(options: ThemeOptions = {}): ThemeInstance {
|
|
|
308
307
|
className.button,
|
|
309
308
|
isActive && className.active
|
|
310
309
|
),
|
|
311
|
-
'data-
|
|
310
|
+
'data-action': 'customTheme',
|
|
312
311
|
'data-theme-button': group.type,
|
|
313
312
|
'data-theme-value': value,
|
|
314
313
|
'aria-selected': isActive ? 'true' : null,
|