vanilla-jui 1.4.8 → 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 +402 -977
- 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 +467 -349
- package/src/components/flow.ts +779 -1133
- package/src/components/form.ts +666 -509
- package/src/components/menu.ts +199 -326
- package/src/components/modal.ts +510 -747
- package/src/components/offcanvas.ts +281 -361
- package/src/components/pagination.ts +207 -374
- package/src/components/swiper.ts +688 -1168
- package/src/components/tabs.ts +468 -788
- 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 -3
- 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/utilities/scheduler.ts +0 -156
package/src/components/menu.ts
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
import { createDeepStore, jsx } from 'vanilla-signal';
|
|
1
|
+
import { For, createDeepStore, flushSync, jsx } from 'vanilla-signal';
|
|
2
2
|
import { t } from 'vanilla-signal-i18n';
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
} from '../core/
|
|
4
|
+
import {
|
|
5
|
+
type FunctionalComponent,
|
|
6
|
+
defineComponent,
|
|
7
|
+
} from '../core/component.ts';
|
|
8
8
|
import locales from '../locales/index.ts';
|
|
9
9
|
import { icon } from '../primitives/icons.ts';
|
|
10
10
|
import { joinClasses } from '../utilities/class-name.ts';
|
|
11
|
-
import {
|
|
11
|
+
import { createEventManager } from '../utilities/events.ts';
|
|
12
12
|
import { randomId } from '../utilities/id.ts';
|
|
13
|
-
import {
|
|
14
|
-
createStateSync,
|
|
15
|
-
stateSnapshot,
|
|
16
|
-
trackStoreVersion,
|
|
17
|
-
} from '../utilities/scheduler.ts';
|
|
18
13
|
import {
|
|
19
14
|
type ResolveSchema,
|
|
20
15
|
resolveProps,
|
|
@@ -66,23 +61,7 @@ interface ResolvedMenuProps extends Record<string, unknown> {
|
|
|
66
61
|
|
|
67
62
|
interface MenuState extends Record<string, unknown> {
|
|
68
63
|
data: MenuItem[];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
interface MenuDOM extends ComponentDOM {
|
|
72
|
-
root: HTMLElement | null;
|
|
73
|
-
list: HTMLElement | null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface MenuRuntime extends ComponentRuntime {
|
|
77
|
-
built: boolean;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
interface MenuCleanupExtras {
|
|
81
|
-
state?: (() => void) | null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
interface MenuSnapshot {
|
|
85
|
-
data: unknown;
|
|
64
|
+
activeKeys: string[];
|
|
86
65
|
}
|
|
87
66
|
|
|
88
67
|
const DEFAULT_CLASS_NAMES: MenuClassNames = {
|
|
@@ -142,6 +121,7 @@ const MENU_PROPS_SCHEMA = {
|
|
|
142
121
|
|
|
143
122
|
const MENU_STATE_SCHEMA = {
|
|
144
123
|
data: MENU_DATA_RULE,
|
|
124
|
+
activeKeys: { type: 'array', items: 'string' },
|
|
145
125
|
};
|
|
146
126
|
|
|
147
127
|
function normalizeProps(input: MenuProps = {}): ResolvedMenuProps {
|
|
@@ -211,320 +191,213 @@ function cloneMenuData(data: unknown): MenuItem[] {
|
|
|
211
191
|
});
|
|
212
192
|
}
|
|
213
193
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function findDirectSubMenu(menuItem: HTMLElement): HTMLElement | null {
|
|
223
|
-
return (
|
|
224
|
-
all<HTMLElement>('[data-menu-list="sub"]', menuItem).find(
|
|
225
|
-
(list) => list.parentElement === menuItem
|
|
226
|
-
) || null
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function findDirectBackItem(subMenu: HTMLElement): HTMLElement | null {
|
|
231
|
-
return (
|
|
232
|
-
all<HTMLElement>('[data-menu-back]', subMenu).find(
|
|
233
|
-
(item) => item.parentElement === subMenu
|
|
234
|
-
) || null
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
class MenuComponent extends Component<ResolvedMenuProps, MenuState, MenuDOM> {
|
|
239
|
-
declare runtime: MenuRuntime;
|
|
240
|
-
declare state: MenuState;
|
|
241
|
-
declare cleanup: Component['cleanup'] & MenuCleanupExtras;
|
|
242
|
-
|
|
243
|
-
constructor(input: MenuProps = {}) {
|
|
244
|
-
const props = normalizeProps(input);
|
|
245
|
-
super(props);
|
|
246
|
-
|
|
247
|
-
this.dom.list = null;
|
|
248
|
-
this.cleanup.state = null;
|
|
249
|
-
this.runtime.built = false;
|
|
250
|
-
|
|
251
|
-
this.state = createDeepStore({
|
|
252
|
-
data: cloneMenuData(props.data),
|
|
253
|
-
}) as MenuState;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
build(): this {
|
|
257
|
-
if (this.runtime.destroyed) {
|
|
258
|
-
throw new Error('Menu.build: instance has been destroyed.');
|
|
259
|
-
}
|
|
260
|
-
if (this.runtime.built) return this;
|
|
261
|
-
|
|
262
|
-
this.validateData(this.state.data);
|
|
263
|
-
this.dom.root = jsx('nav', {
|
|
264
|
-
className: this.props.className.root,
|
|
265
|
-
'data-menu': 'root',
|
|
266
|
-
...(this.props.type !== undefined && {
|
|
267
|
-
'data-menu-type': this.props.type,
|
|
268
|
-
}),
|
|
269
|
-
children: jsx('ul', {
|
|
270
|
-
className: this.props.className.list,
|
|
271
|
-
id: this.props.id,
|
|
272
|
-
'data-menu-list': 'root',
|
|
273
|
-
}),
|
|
274
|
-
}) as HTMLElement;
|
|
275
|
-
this.dom.list = this.dom.root.querySelector('[data-menu-list="root"]');
|
|
276
|
-
this.runtime.built = true;
|
|
277
|
-
this.bindState();
|
|
278
|
-
this.bindEvents();
|
|
279
|
-
this.emit('init', this.props);
|
|
280
|
-
return this;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
private bindState(): void {
|
|
284
|
-
if (this.cleanup.state) return;
|
|
285
|
-
|
|
286
|
-
this.cleanup.state = createStateSync(
|
|
287
|
-
() => ({
|
|
288
|
-
data: trackStoreVersion(this.state.data),
|
|
289
|
-
}),
|
|
290
|
-
(snapshot) => this.renderSnapshot(snapshot),
|
|
291
|
-
{ deferInitial: false, flushInitial: true }
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
private renderSnapshot(snapshot: MenuSnapshot): void {
|
|
296
|
-
if (!this.runtime.built || !this.dom.root || !this.dom.list) return;
|
|
297
|
-
this.validateData(snapshot.data);
|
|
298
|
-
const data = cloneMenuData(stateSnapshot(snapshot.data));
|
|
299
|
-
|
|
300
|
-
this.dom.list.textContent = '';
|
|
301
|
-
this.dom.list.append(...data.map((item) => this.buildItem(item)));
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private buildItem(item: MenuItem): HTMLElement {
|
|
305
|
-
const { className } = this.props;
|
|
306
|
-
const childrenItems = item.children || [];
|
|
307
|
-
const hasChildren = childrenItems.length > 0;
|
|
308
|
-
const classes = [className.item];
|
|
309
|
-
|
|
310
|
-
if (hasChildren) classes.push(className.hasChildren);
|
|
311
|
-
classes.push(...normalizeItemClasses(item.classes));
|
|
312
|
-
|
|
313
|
-
const url = itemUrl(item.url);
|
|
314
|
-
const shouldRenderSpan = !hasChildren && !url;
|
|
315
|
-
const title = shouldRenderSpan
|
|
316
|
-
? (jsx('span', {
|
|
317
|
-
className: className.link,
|
|
318
|
-
'data-menu-link': '',
|
|
319
|
-
children: item.title,
|
|
320
|
-
}) as HTMLElement)
|
|
321
|
-
: (jsx('a', {
|
|
322
|
-
className: className.link,
|
|
323
|
-
href: url,
|
|
324
|
-
...(item.target && { target: item.target }),
|
|
325
|
-
'data-menu-link': '',
|
|
326
|
-
children: item.title,
|
|
327
|
-
}) as HTMLElement);
|
|
328
|
-
|
|
329
|
-
const children: Node[] = [title];
|
|
330
|
-
|
|
331
|
-
if (hasChildren) {
|
|
332
|
-
children.push(
|
|
333
|
-
jsx('ul', {
|
|
334
|
-
className: className.subMenu,
|
|
335
|
-
'data-menu-list': 'sub',
|
|
336
|
-
children: childrenItems.map((child) => this.buildItem(child)),
|
|
337
|
-
}) as HTMLElement
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return jsx('li', {
|
|
342
|
-
className: joinClasses(...classes),
|
|
343
|
-
id: itemId(item.id),
|
|
344
|
-
'data-menu-item': item.id == null ? '' : String(item.id),
|
|
345
|
-
...(hasChildren && { 'data-menu-has-children': '' }),
|
|
346
|
-
children,
|
|
347
|
-
}) as HTMLElement;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
private bindEvents(): void {
|
|
351
|
-
this.unbindEvents();
|
|
352
|
-
const root = this.dom.root;
|
|
353
|
-
if (!root) return;
|
|
354
|
-
|
|
355
|
-
if (this.props.type === 'mobile') {
|
|
356
|
-
this.cleanup.events.on('mobile', root, 'click', (event) => {
|
|
357
|
-
const target = event.target;
|
|
358
|
-
if (!(target instanceof Element)) return;
|
|
359
|
-
|
|
360
|
-
const backItem = target.closest<HTMLElement>('[data-menu-back]');
|
|
361
|
-
if (backItem && root.contains(backItem)) {
|
|
362
|
-
event.preventDefault();
|
|
363
|
-
this.handleBack(backItem);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const menuItem = target.closest<HTMLElement>(
|
|
368
|
-
'[data-menu-has-children]'
|
|
369
|
-
);
|
|
370
|
-
if (!menuItem || !root.contains(menuItem)) return;
|
|
371
|
-
|
|
372
|
-
const subMenu = findDirectSubMenu(menuItem);
|
|
373
|
-
if (subMenu?.contains(target)) return;
|
|
374
|
-
|
|
375
|
-
const directLink = findDirectMenuLink(menuItem);
|
|
376
|
-
if (
|
|
377
|
-
target === menuItem ||
|
|
378
|
-
(directLink && (target === directLink || directLink.contains(target)))
|
|
379
|
-
) {
|
|
380
|
-
event.preventDefault();
|
|
381
|
-
this.handleMenuClick(menuItem);
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (this.props.type === 'bottom') {
|
|
387
|
-
this.cleanup.events.on('bottom', document, 'click', (event) => {
|
|
388
|
-
const target = event.target;
|
|
389
|
-
if (!(target instanceof Element) || !root.isConnected) return;
|
|
390
|
-
|
|
391
|
-
const submenuLink = target.closest(
|
|
392
|
-
'[data-menu-list="sub"] [data-menu-link]'
|
|
393
|
-
);
|
|
394
|
-
if (submenuLink && root.contains(submenuLink)) return;
|
|
395
|
-
|
|
396
|
-
const firstLevelMenuItem = target.closest<HTMLElement>(
|
|
397
|
-
'[data-menu-list="root"] > [data-menu-has-children]'
|
|
398
|
-
);
|
|
194
|
+
export type Menu = FunctionalComponent<
|
|
195
|
+
ResolvedMenuProps,
|
|
196
|
+
MenuState,
|
|
197
|
+
HTMLElement
|
|
198
|
+
>;
|
|
399
199
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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];
|
|
224
|
+
});
|
|
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
|
+
};
|
|
422
236
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
className: className.backItem,
|
|
237
|
+
const backView = (key: string): HTMLElement =>
|
|
238
|
+
jsx('li', {
|
|
239
|
+
className: props.className.backItem,
|
|
426
240
|
'data-menu-back': '',
|
|
427
241
|
children: jsx('a', {
|
|
428
|
-
className: className.link,
|
|
242
|
+
className: props.className.link,
|
|
429
243
|
href: '',
|
|
430
244
|
'data-menu-link': '',
|
|
245
|
+
onClick: (event: Event) => {
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
close(key);
|
|
248
|
+
},
|
|
431
249
|
children: [
|
|
432
250
|
jsx('span', {
|
|
433
|
-
className: className.backIcon,
|
|
251
|
+
className: props.className.backIcon,
|
|
434
252
|
children: icon('arrow-left'),
|
|
435
253
|
}),
|
|
436
254
|
jsx('span', {
|
|
437
|
-
className: className.text,
|
|
438
|
-
children:
|
|
255
|
+
className: props.className.text,
|
|
256
|
+
children: props.backText,
|
|
439
257
|
}),
|
|
440
258
|
],
|
|
441
259
|
}),
|
|
442
260
|
}) as HTMLElement;
|
|
443
261
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
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;
|
|
525
355
|
|
|
526
|
-
|
|
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
|
+
});
|
|
527
401
|
|
|
528
|
-
|
|
529
|
-
return new MenuComponent(input);
|
|
402
|
+
return menu;
|
|
530
403
|
}
|