vanilla-jui 1.4.7 → 1.5.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/README.md +10 -4
- package/dist/index.d.ts +418 -970
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +3 -3
- package/src/components/accordion.ts +466 -346
- package/src/components/flow.ts +779 -1133
- package/src/components/form.ts +666 -509
- package/src/components/menu.ts +198 -328
- package/src/components/modal.ts +510 -747
- package/src/components/offcanvas.ts +273 -367
- package/src/components/pagination.ts +201 -375
- package/src/components/swiper.ts +689 -1107
- package/src/components/tabs.ts +467 -778
- package/src/components/toc.ts +191 -244
- package/src/core/component.ts +332 -0
- package/src/core/motion.ts +298 -0
- package/src/core/presence.ts +233 -0
- package/src/core/scheduler.ts +91 -0
- package/src/core/view.ts +31 -0
- package/src/css/accordion.css +94 -3
- package/src/css/content.css +18 -24
- package/src/css/drop.css +4 -1
- package/src/css/form.css +8 -0
- package/src/css/menu.css +10 -5
- package/src/css/modal.css +2 -1
- package/src/css/offcanvas.css +5 -17
- package/src/css/swiper.css +2 -1
- package/src/css/tabs.css +8 -17
- package/src/css/themes/default/accordion.css +0 -48
- package/src/css/themes/default/swiper.css +1 -1
- package/src/css/themes/default/tabs.css +4 -6
- package/src/css/toast.css +1 -17
- package/src/css/tooltip.css +2 -1
- package/src/index.ts +8 -2
- package/src/locales/index.ts +3 -0
- package/src/primitives/drop.ts +253 -262
- package/src/primitives/loading.ts +22 -4
- package/src/primitives/parabola.ts +123 -164
- package/src/primitives/sticky.ts +297 -0
- package/src/primitives/theme.ts +164 -222
- package/src/primitives/toast.ts +309 -284
- package/src/primitives/tooltip.ts +65 -66
- package/src/utilities/browser.ts +38 -0
- package/src/utilities/dom.ts +0 -54
- package/src/utilities/refs.ts +61 -0
- package/src/utilities/state.ts +73 -0
- package/src/utilities/timer.ts +11 -31
- package/src/validation/validator.ts +289 -389
- package/src/components/sticky.ts +0 -297
- package/src/core/Component.ts +0 -377
package/src/components/menu.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createDeepStore,
|
|
3
|
-
createEffect,
|
|
4
|
-
createRoot,
|
|
5
|
-
jsx,
|
|
6
|
-
untrack,
|
|
7
|
-
} from 'vanilla-signal';
|
|
1
|
+
import { For, createDeepStore, flushSync, jsx } from 'vanilla-signal';
|
|
8
2
|
import { t } from 'vanilla-signal-i18n';
|
|
9
3
|
|
|
10
|
-
import
|
|
11
|
-
type
|
|
12
|
-
|
|
13
|
-
} from '../core/
|
|
4
|
+
import {
|
|
5
|
+
type FunctionalComponent,
|
|
6
|
+
defineComponent,
|
|
7
|
+
} from '../core/component.ts';
|
|
14
8
|
import locales from '../locales/index.ts';
|
|
15
9
|
import { icon } from '../primitives/icons.ts';
|
|
16
10
|
import { joinClasses } from '../utilities/class-name.ts';
|
|
17
|
-
import {
|
|
11
|
+
import { createEventManager } from '../utilities/events.ts';
|
|
18
12
|
import { randomId } from '../utilities/id.ts';
|
|
19
13
|
import {
|
|
20
14
|
type ResolveSchema,
|
|
@@ -67,23 +61,7 @@ interface ResolvedMenuProps extends Record<string, unknown> {
|
|
|
67
61
|
|
|
68
62
|
interface MenuState extends Record<string, unknown> {
|
|
69
63
|
data: MenuItem[];
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
interface MenuDOM extends ComponentDOM {
|
|
73
|
-
root: HTMLElement | null;
|
|
74
|
-
list: HTMLElement | null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
interface MenuRuntime extends ComponentRuntime {
|
|
78
|
-
built: boolean;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
interface MenuCleanupExtras {
|
|
82
|
-
state?: (() => void) | null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
interface MenuSnapshot {
|
|
86
|
-
data: unknown;
|
|
64
|
+
activeKeys: string[];
|
|
87
65
|
}
|
|
88
66
|
|
|
89
67
|
const DEFAULT_CLASS_NAMES: MenuClassNames = {
|
|
@@ -143,6 +121,7 @@ const MENU_PROPS_SCHEMA = {
|
|
|
143
121
|
|
|
144
122
|
const MENU_STATE_SCHEMA = {
|
|
145
123
|
data: MENU_DATA_RULE,
|
|
124
|
+
activeKeys: { type: 'array', items: 'string' },
|
|
146
125
|
};
|
|
147
126
|
|
|
148
127
|
function normalizeProps(input: MenuProps = {}): ResolvedMenuProps {
|
|
@@ -212,322 +191,213 @@ function cloneMenuData(data: unknown): MenuItem[] {
|
|
|
212
191
|
});
|
|
213
192
|
}
|
|
214
193
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function findDirectSubMenu(menuItem: HTMLElement): HTMLElement | null {
|
|
224
|
-
return (
|
|
225
|
-
all<HTMLElement>('[data-menu-list="sub"]', menuItem).find(
|
|
226
|
-
(list) => list.parentElement === menuItem
|
|
227
|
-
) || null
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function findDirectBackItem(subMenu: HTMLElement): HTMLElement | null {
|
|
232
|
-
return (
|
|
233
|
-
all<HTMLElement>('[data-menu-back]', subMenu).find(
|
|
234
|
-
(item) => item.parentElement === subMenu
|
|
235
|
-
) || null
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
class MenuComponent extends Component<ResolvedMenuProps, MenuState, MenuDOM> {
|
|
240
|
-
declare runtime: MenuRuntime;
|
|
241
|
-
declare state: MenuState;
|
|
242
|
-
declare cleanup: Component['cleanup'] & MenuCleanupExtras;
|
|
243
|
-
|
|
244
|
-
constructor(input: MenuProps = {}) {
|
|
245
|
-
const props = normalizeProps(input);
|
|
246
|
-
super(props);
|
|
247
|
-
|
|
248
|
-
this.dom.list = null;
|
|
249
|
-
this.cleanup.state = null;
|
|
250
|
-
this.runtime.built = false;
|
|
251
|
-
|
|
252
|
-
this.state = createDeepStore({
|
|
253
|
-
data: cloneMenuData(props.data),
|
|
254
|
-
}) as MenuState;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
build(): this {
|
|
258
|
-
if (this.runtime.destroyed) {
|
|
259
|
-
throw new Error('Menu.build: instance has been destroyed.');
|
|
260
|
-
}
|
|
261
|
-
if (this.runtime.built) return this;
|
|
262
|
-
|
|
263
|
-
this.validateData(this.state.data);
|
|
264
|
-
this.dom.root = jsx('nav', {
|
|
265
|
-
className: this.props.className.root,
|
|
266
|
-
'data-menu': 'root',
|
|
267
|
-
...(this.props.type !== undefined && {
|
|
268
|
-
'data-menu-type': this.props.type,
|
|
269
|
-
}),
|
|
270
|
-
children: jsx('ul', {
|
|
271
|
-
className: this.props.className.list,
|
|
272
|
-
id: this.props.id,
|
|
273
|
-
'data-menu-list': 'root',
|
|
274
|
-
}),
|
|
275
|
-
}) as HTMLElement;
|
|
276
|
-
this.dom.list = this.dom.root.querySelector('[data-menu-list="root"]');
|
|
277
|
-
this.runtime.built = true;
|
|
278
|
-
this.bindState();
|
|
279
|
-
this.bindEvents();
|
|
280
|
-
this.emit('init', this.props);
|
|
281
|
-
return this;
|
|
282
|
-
}
|
|
194
|
+
export type Menu = FunctionalComponent<
|
|
195
|
+
ResolvedMenuProps,
|
|
196
|
+
MenuState,
|
|
197
|
+
HTMLElement
|
|
198
|
+
>;
|
|
283
199
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
200
|
+
export function createMenu(input: MenuProps = {}): Menu {
|
|
201
|
+
const props = normalizeProps(input);
|
|
202
|
+
const state = createDeepStore({
|
|
203
|
+
data: cloneMenuData(props.data),
|
|
204
|
+
activeKeys: [],
|
|
205
|
+
}) as MenuState;
|
|
206
|
+
const generatedKeys = new WeakMap<object, string>();
|
|
207
|
+
const events = createEventManager();
|
|
208
|
+
let menu: Menu;
|
|
209
|
+
|
|
210
|
+
const itemKey = (item: MenuItem): string => {
|
|
211
|
+
if (item.id != null) return String(item.id);
|
|
212
|
+
if (!generatedKeys.has(item)) generatedKeys.set(item, randomId());
|
|
213
|
+
return generatedKeys.get(item) as string;
|
|
214
|
+
};
|
|
215
|
+
const isActive = (key: string): boolean => state.activeKeys.includes(key);
|
|
216
|
+
const open = (key: string): void => {
|
|
217
|
+
flushSync(() => {
|
|
218
|
+
state.activeKeys =
|
|
219
|
+
props.type === 'bottom'
|
|
220
|
+
? [key]
|
|
221
|
+
: state.activeKeys.includes(key)
|
|
222
|
+
? state.activeKeys
|
|
223
|
+
: [...state.activeKeys, key];
|
|
295
224
|
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
private buildItem(item: MenuItem): HTMLElement {
|
|
308
|
-
const { className } = this.props;
|
|
309
|
-
const childrenItems = item.children || [];
|
|
310
|
-
const hasChildren = childrenItems.length > 0;
|
|
311
|
-
const classes = [className.item];
|
|
312
|
-
|
|
313
|
-
if (hasChildren) classes.push(className.hasChildren);
|
|
314
|
-
classes.push(...normalizeItemClasses(item.classes));
|
|
315
|
-
|
|
316
|
-
const url = itemUrl(item.url);
|
|
317
|
-
const shouldRenderSpan = !hasChildren && !url;
|
|
318
|
-
const title = shouldRenderSpan
|
|
319
|
-
? (jsx('span', {
|
|
320
|
-
className: className.link,
|
|
321
|
-
'data-menu-link': '',
|
|
322
|
-
children: item.title,
|
|
323
|
-
}) as HTMLElement)
|
|
324
|
-
: (jsx('a', {
|
|
325
|
-
className: className.link,
|
|
326
|
-
href: url,
|
|
327
|
-
...(item.target && { target: item.target }),
|
|
328
|
-
'data-menu-link': '',
|
|
329
|
-
children: item.title,
|
|
330
|
-
}) as HTMLElement);
|
|
331
|
-
|
|
332
|
-
const children: Node[] = [title];
|
|
333
|
-
|
|
334
|
-
if (hasChildren) {
|
|
335
|
-
children.push(
|
|
336
|
-
jsx('ul', {
|
|
337
|
-
className: className.subMenu,
|
|
338
|
-
'data-menu-list': 'sub',
|
|
339
|
-
children: childrenItems.map((child) => this.buildItem(child)),
|
|
340
|
-
}) as HTMLElement
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return jsx('li', {
|
|
345
|
-
className: joinClasses(...classes),
|
|
346
|
-
id: itemId(item.id),
|
|
347
|
-
'data-menu-item': item.id == null ? '' : String(item.id),
|
|
348
|
-
...(hasChildren && { 'data-menu-has-children': '' }),
|
|
349
|
-
children,
|
|
350
|
-
}) as HTMLElement;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
private bindEvents(): void {
|
|
354
|
-
this.unbindEvents();
|
|
355
|
-
const root = this.dom.root;
|
|
356
|
-
if (!root) return;
|
|
357
|
-
|
|
358
|
-
if (this.props.type === 'mobile') {
|
|
359
|
-
this.cleanup.events.on('mobile', root, 'click', (event) => {
|
|
360
|
-
const target = event.target;
|
|
361
|
-
if (!(target instanceof Element)) return;
|
|
362
|
-
|
|
363
|
-
const backItem = target.closest<HTMLElement>('[data-menu-back]');
|
|
364
|
-
if (backItem && root.contains(backItem)) {
|
|
365
|
-
event.preventDefault();
|
|
366
|
-
this.handleBack(backItem);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const menuItem = target.closest<HTMLElement>(
|
|
371
|
-
'[data-menu-has-children]'
|
|
372
|
-
);
|
|
373
|
-
if (!menuItem || !root.contains(menuItem)) return;
|
|
374
|
-
|
|
375
|
-
const subMenu = findDirectSubMenu(menuItem);
|
|
376
|
-
if (subMenu?.contains(target)) return;
|
|
377
|
-
|
|
378
|
-
const directLink = findDirectMenuLink(menuItem);
|
|
379
|
-
if (
|
|
380
|
-
target === menuItem ||
|
|
381
|
-
(directLink && (target === directLink || directLink.contains(target)))
|
|
382
|
-
) {
|
|
383
|
-
event.preventDefault();
|
|
384
|
-
this.handleMenuClick(menuItem);
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (this.props.type === 'bottom') {
|
|
390
|
-
this.cleanup.events.on('bottom', document, 'click', (event) => {
|
|
391
|
-
const target = event.target;
|
|
392
|
-
if (!(target instanceof Element) || !root.isConnected) return;
|
|
393
|
-
|
|
394
|
-
const submenuLink = target.closest(
|
|
395
|
-
'[data-menu-list="sub"] [data-menu-link]'
|
|
396
|
-
);
|
|
397
|
-
if (submenuLink && root.contains(submenuLink)) return;
|
|
398
|
-
|
|
399
|
-
const firstLevelMenuItem = target.closest<HTMLElement>(
|
|
400
|
-
'[data-menu-list="root"] > [data-menu-has-children]'
|
|
401
|
-
);
|
|
402
|
-
|
|
403
|
-
if (firstLevelMenuItem && root.contains(firstLevelMenuItem)) {
|
|
404
|
-
event.preventDefault();
|
|
405
|
-
this.toggleActive(firstLevelMenuItem);
|
|
406
|
-
} else {
|
|
407
|
-
this.clearActive();
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
private unbindEvents(): void {
|
|
414
|
-
this.cleanup.events.clear();
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
private handleMenuClick(menuItem: HTMLElement): void {
|
|
418
|
-
menuItem.classList.add(this.props.className.active);
|
|
419
|
-
|
|
420
|
-
const subMenu = findDirectSubMenu(menuItem);
|
|
421
|
-
if (!subMenu) return;
|
|
422
|
-
|
|
423
|
-
const existingBack = findDirectBackItem(subMenu);
|
|
424
|
-
if (existingBack) return;
|
|
225
|
+
};
|
|
226
|
+
const close = (key: string): void => {
|
|
227
|
+
flushSync(() => {
|
|
228
|
+
state.activeKeys = state.activeKeys.filter((item) => item !== key);
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
const toggle = (key: string): void => {
|
|
232
|
+
flushSync(() => {
|
|
233
|
+
state.activeKeys = isActive(key) ? [] : [key];
|
|
234
|
+
});
|
|
235
|
+
};
|
|
425
236
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
className: className.backItem,
|
|
237
|
+
const backView = (key: string): HTMLElement =>
|
|
238
|
+
jsx('li', {
|
|
239
|
+
className: props.className.backItem,
|
|
429
240
|
'data-menu-back': '',
|
|
430
241
|
children: jsx('a', {
|
|
431
|
-
className: className.link,
|
|
242
|
+
className: props.className.link,
|
|
432
243
|
href: '',
|
|
433
244
|
'data-menu-link': '',
|
|
245
|
+
onClick: (event: Event) => {
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
close(key);
|
|
248
|
+
},
|
|
434
249
|
children: [
|
|
435
250
|
jsx('span', {
|
|
436
|
-
className: className.backIcon,
|
|
251
|
+
className: props.className.backIcon,
|
|
437
252
|
children: icon('arrow-left'),
|
|
438
253
|
}),
|
|
439
254
|
jsx('span', {
|
|
440
|
-
className: className.text,
|
|
441
|
-
children:
|
|
255
|
+
className: props.className.text,
|
|
256
|
+
children: props.backText,
|
|
442
257
|
}),
|
|
443
258
|
],
|
|
444
259
|
}),
|
|
445
260
|
}) as HTMLElement;
|
|
446
261
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
262
|
+
const listView = (
|
|
263
|
+
items: () => MenuItem[],
|
|
264
|
+
root = false,
|
|
265
|
+
parentKey?: string
|
|
266
|
+
): HTMLElement =>
|
|
267
|
+
jsx('ul', {
|
|
268
|
+
className: root ? props.className.list : props.className.subMenu,
|
|
269
|
+
...(root ? { id: props.id } : {}),
|
|
270
|
+
'data-menu-list': root ? 'root' : 'sub',
|
|
271
|
+
children: [
|
|
272
|
+
For({
|
|
273
|
+
each: () =>
|
|
274
|
+
props.type === 'mobile' && parentKey && isActive(parentKey)
|
|
275
|
+
? [parentKey]
|
|
276
|
+
: [],
|
|
277
|
+
key: (key: string) => key,
|
|
278
|
+
children: (key: () => string) => backView(key()),
|
|
279
|
+
}),
|
|
280
|
+
For({
|
|
281
|
+
each: items,
|
|
282
|
+
key: (item: MenuItem) => itemKey(item),
|
|
283
|
+
children: (itemAccessor: () => MenuItem) => {
|
|
284
|
+
const key = itemKey(itemAccessor());
|
|
285
|
+
return jsx('li', {
|
|
286
|
+
className: () => {
|
|
287
|
+
const item = itemAccessor();
|
|
288
|
+
return joinClasses(
|
|
289
|
+
props.className.item,
|
|
290
|
+
item.children?.length ? props.className.hasChildren : '',
|
|
291
|
+
...normalizeItemClasses(item.classes),
|
|
292
|
+
isActive(key) ? props.className.active : ''
|
|
293
|
+
);
|
|
294
|
+
},
|
|
295
|
+
id: () =>
|
|
296
|
+
itemAccessor().id == null
|
|
297
|
+
? `menu-item-${key}`
|
|
298
|
+
: itemId(itemAccessor().id),
|
|
299
|
+
'data-menu-item': () =>
|
|
300
|
+
itemAccessor().id == null ? '' : String(itemAccessor().id),
|
|
301
|
+
'data-menu-has-children': () =>
|
|
302
|
+
itemAccessor().children?.length ? '' : null,
|
|
303
|
+
onClick: (event: Event) => {
|
|
304
|
+
if (
|
|
305
|
+
props.type === 'mobile' &&
|
|
306
|
+
event.target === event.currentTarget &&
|
|
307
|
+
itemAccessor().children?.length
|
|
308
|
+
) {
|
|
309
|
+
open(key);
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
children: [
|
|
313
|
+
() => {
|
|
314
|
+
const item = itemAccessor();
|
|
315
|
+
const hasChildren = !!item.children?.length;
|
|
316
|
+
const url = itemUrl(item.url);
|
|
317
|
+
if (!hasChildren && !url) {
|
|
318
|
+
return jsx('span', {
|
|
319
|
+
className: props.className.link,
|
|
320
|
+
'data-menu-link': '',
|
|
321
|
+
children: item.title,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return jsx('a', {
|
|
325
|
+
className: props.className.link,
|
|
326
|
+
href: url,
|
|
327
|
+
target: item.target,
|
|
328
|
+
'data-menu-link': '',
|
|
329
|
+
onClick: (event: Event) => {
|
|
330
|
+
if (!hasChildren) return;
|
|
331
|
+
if (props.type === 'mobile' || props.type === 'bottom') {
|
|
332
|
+
event.preventDefault();
|
|
333
|
+
if (props.type === 'bottom') toggle(key);
|
|
334
|
+
else open(key);
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
children: item.title,
|
|
338
|
+
});
|
|
339
|
+
},
|
|
340
|
+
() => {
|
|
341
|
+
const children = itemAccessor().children;
|
|
342
|
+
if (!children?.length) return null;
|
|
343
|
+
return listView(
|
|
344
|
+
() => itemAccessor().children || [],
|
|
345
|
+
false,
|
|
346
|
+
key
|
|
347
|
+
);
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
}),
|
|
353
|
+
],
|
|
354
|
+
}) as HTMLElement;
|
|
528
355
|
|
|
529
|
-
|
|
356
|
+
menu = defineComponent({
|
|
357
|
+
name: 'Menu',
|
|
358
|
+
props,
|
|
359
|
+
state,
|
|
360
|
+
normalizeStatePatch(patch) {
|
|
361
|
+
const next = { ...patch };
|
|
362
|
+
if (Object.hasOwn(next, 'data')) next.data = cloneMenuData(next.data);
|
|
363
|
+
return next;
|
|
364
|
+
},
|
|
365
|
+
validateStatePatch(patch) {
|
|
366
|
+
for (const key of Object.keys(patch)) {
|
|
367
|
+
if (!Object.hasOwn(MENU_STATE_SCHEMA, key)) {
|
|
368
|
+
throw new Error(`Menu.setState: "${key}" is not supported.`);
|
|
369
|
+
}
|
|
370
|
+
const stateKey = key as keyof typeof MENU_STATE_SCHEMA;
|
|
371
|
+
validateParam(
|
|
372
|
+
key,
|
|
373
|
+
patch[key as keyof MenuState],
|
|
374
|
+
MENU_STATE_SCHEMA[stateKey],
|
|
375
|
+
'Menu.setState'
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
view: () => {
|
|
380
|
+
validateParam('data', state.data, MENU_DATA_RULE, 'Menu');
|
|
381
|
+
return jsx('nav', {
|
|
382
|
+
className: props.className.root,
|
|
383
|
+
'data-menu': 'root',
|
|
384
|
+
'data-menu-type': props.type,
|
|
385
|
+
children: listView(() => state.data, true),
|
|
386
|
+
}) as HTMLElement;
|
|
387
|
+
},
|
|
388
|
+
onBuild(context) {
|
|
389
|
+
if (props.type !== 'bottom') return;
|
|
390
|
+
events.on('outside', document, 'click', (event) => {
|
|
391
|
+
const target = event.target;
|
|
392
|
+
if (!(target instanceof Node) || context.element?.contains(target))
|
|
393
|
+
return;
|
|
394
|
+
flushSync(() => {
|
|
395
|
+
state.activeKeys = [];
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
context.own(() => events.clear());
|
|
399
|
+
},
|
|
400
|
+
});
|
|
530
401
|
|
|
531
|
-
|
|
532
|
-
return new MenuComponent(input);
|
|
402
|
+
return menu;
|
|
533
403
|
}
|