@lytjs/ui 6.4.0 → 6.5.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.
package/dist/index.mjs CHANGED
@@ -252,6 +252,14 @@ var Input = defineComponent({
252
252
  };
253
253
  }
254
254
  });
255
+ var FOCUSABLE_SELECTORS = [
256
+ "a[href]",
257
+ "button:not([disabled])",
258
+ "input:not([disabled])",
259
+ "select:not([disabled])",
260
+ "textarea:not([disabled])",
261
+ '[tabindex]:not([tabindex="-1"])'
262
+ ].join(", ");
255
263
  var Dialog = defineComponent({
256
264
  name: "LytDialog",
257
265
  props: {
@@ -267,6 +275,8 @@ var Dialog = defineComponent({
267
275
  ariaLabel: { type: String, default: "" },
268
276
  ariaDescribedBy: { type: String, default: "" },
269
277
  ariaModal: { type: Boolean, default: true },
278
+ initialFocus: { type: [String, Object], default: void 0 },
279
+ returnFocusOnClose: { type: Boolean, default: true },
270
280
  onBeforeOpen: { type: Function, default: void 0 },
271
281
  onBeforeClose: { type: Function, default: void 0 },
272
282
  onOpen: { type: Function, default: void 0 },
@@ -278,6 +288,8 @@ var Dialog = defineComponent({
278
288
  setup(props, { slots }) {
279
289
  const p = props;
280
290
  const visible = signal(p.modelValue);
291
+ const dialogRef = signal(null);
292
+ const previousActiveElement = signal(null);
281
293
  watch(() => p.modelValue, (newVal) => {
282
294
  visible.set(newVal);
283
295
  if (newVal) {
@@ -291,6 +303,12 @@ var Dialog = defineComponent({
291
303
  }
292
304
  visible.set(false);
293
305
  p.onClose?.();
306
+ if (p.returnFocusOnClose) {
307
+ const prevEl = previousActiveElement();
308
+ if (prevEl && prevEl.focus) {
309
+ prevEl.focus();
310
+ }
311
+ }
294
312
  };
295
313
  const handleKeydown = (e) => {
296
314
  if (p.closeOnPressEscape && e.key === "Escape") {
@@ -299,6 +317,58 @@ var Dialog = defineComponent({
299
317
  }
300
318
  p.onKeydown?.(e);
301
319
  };
320
+ const getFocusableElements = (container) => {
321
+ return Array.from(
322
+ container.querySelectorAll(FOCUSABLE_SELECTORS)
323
+ ).filter((el) => {
324
+ return el.offsetParent !== null && !el.hasAttribute("aria-hidden");
325
+ });
326
+ };
327
+ const handleTabKey = (e) => {
328
+ const dialog = dialogRef();
329
+ if (!dialog) return;
330
+ const focusableElements = getFocusableElements(dialog);
331
+ if (focusableElements.length === 0) return;
332
+ const firstElement = focusableElements[0];
333
+ const lastElement = focusableElements[focusableElements.length - 1];
334
+ if (e.shiftKey) {
335
+ if (document.activeElement === firstElement) {
336
+ e.preventDefault();
337
+ lastElement.focus();
338
+ }
339
+ } else {
340
+ if (document.activeElement === lastElement) {
341
+ e.preventDefault();
342
+ firstElement.focus();
343
+ }
344
+ }
345
+ };
346
+ effect(() => {
347
+ if (visible()) {
348
+ previousActiveElement.set(document.activeElement);
349
+ setTimeout(() => {
350
+ const dialog = dialogRef();
351
+ if (dialog) {
352
+ if (p.initialFocus) {
353
+ const initialEl = typeof p.initialFocus === "string" ? dialog.querySelector(p.initialFocus) : p.initialFocus;
354
+ if (initialEl && initialEl.focus) {
355
+ initialEl.focus();
356
+ } else {
357
+ const focusableElements = getFocusableElements(dialog);
358
+ if (focusableElements.length > 0) {
359
+ focusableElements[0].focus();
360
+ }
361
+ }
362
+ } else {
363
+ const focusableElements = getFocusableElements(dialog);
364
+ if (focusableElements.length > 0) {
365
+ focusableElements[0].focus();
366
+ }
367
+ }
368
+ }
369
+ }, 0);
370
+ }
371
+ });
302
372
  return () => {
303
373
  if (!visible()) {
304
374
  return createVNode("div", { style: "display: none;" }, []);
@@ -355,7 +425,15 @@ var Dialog = defineComponent({
355
425
  });
356
426
  return createVNode("div", {
357
427
  class: "lyt-dialog__wrapper",
358
- onKeydown: handleKeydown
428
+ onKeydown: (e) => {
429
+ if (e.key === "Tab") {
430
+ handleTabKey(e);
431
+ }
432
+ handleKeydown(e);
433
+ },
434
+ ref: (el) => {
435
+ dialogRef.set(el);
436
+ }
359
437
  }, [
360
438
  createVNode("div", {
361
439
  class: "lyt-dialog__overlay",
@@ -364,12 +442,22 @@ var Dialog = defineComponent({
364
442
  }),
365
443
  createVNode("div", mergeA11yProps(dialogA11yProps, {
366
444
  class: dialogClass,
367
- style: dialogStyle
445
+ style: dialogStyle,
446
+ role: "dialog",
447
+ "aria-modal": p.ariaModal
368
448
  }), children)
369
449
  ]);
370
450
  };
371
451
  }
372
452
  });
453
+ var FOCUSABLE_SELECTORS2 = [
454
+ "a[href]",
455
+ "button:not([disabled])",
456
+ "input:not([disabled])",
457
+ "select:not([disabled])",
458
+ "textarea:not([disabled])",
459
+ '[tabindex]:not([tabindex="-1"])'
460
+ ].join(", ");
373
461
  var Select = defineComponent({
374
462
  name: "LytSelect",
375
463
  props: {
@@ -387,6 +475,8 @@ var Select = defineComponent({
387
475
  ariaInvalid: { type: Boolean, default: false },
388
476
  ariaRequired: { type: Boolean, default: false },
389
477
  tabIndex: { type: Number, default: void 0 },
478
+ closeOnSelect: { type: Boolean, default: true },
479
+ filterable: { type: Boolean, default: false },
390
480
  onChange: { type: Function, default: void 0 },
391
481
  onClear: { type: Function, default: void 0 },
392
482
  onKeydown: { type: Function, default: void 0 },
@@ -398,6 +488,11 @@ var Select = defineComponent({
398
488
  const selectedValue = signal(/* @__PURE__ */ new Set());
399
489
  const searchValue = signal("");
400
490
  const highlightedIndex = signal(-1);
491
+ const triggerRef = signal(null);
492
+ const dropdownRef = signal(null);
493
+ const previousActiveElement = signal(null);
494
+ const listboxId = signal(`lyt-select-listbox-${Math.random().toString(36).substr(2, 9)}`);
495
+ const activeDescendant = signal("");
401
496
  effect(() => {
402
497
  if (Array.isArray(p.modelValue)) {
403
498
  selectedValue.set(new Set(p.modelValue));
@@ -408,11 +503,22 @@ var Select = defineComponent({
408
503
  const getFilteredOptions = () => {
409
504
  const opts = p.options || [];
410
505
  const query = searchValue();
411
- return query ? opts.filter((opt) => opt.label.includes(query)) : opts;
506
+ if (!p.filterable || !query) return opts;
507
+ const lowerQuery = query.toLowerCase();
508
+ return opts.filter(
509
+ (opt) => opt.label.toLowerCase().includes(lowerQuery)
510
+ );
412
511
  };
413
512
  const getEnabledOptions = () => {
414
513
  return getFilteredOptions().filter((opt) => !opt.disabled);
415
514
  };
515
+ const getFocusableElements = (container) => {
516
+ return Array.from(
517
+ container.querySelectorAll(FOCUSABLE_SELECTORS2)
518
+ ).filter((el) => {
519
+ return el.offsetParent !== null && !el.hasAttribute("aria-hidden");
520
+ });
521
+ };
416
522
  const toggleDropdown = () => {
417
523
  if (p.disabled) return;
418
524
  const newOpen = !isOpen();
@@ -427,6 +533,25 @@ var Select = defineComponent({
427
533
  currentValue = foundIndex >= 0 ? foundIndex : 0;
428
534
  }
429
535
  highlightedIndex.set(currentValue);
536
+ const option = enabledOptions[currentValue];
537
+ if (option) {
538
+ activeDescendant.set(`lyt-select-option-${option.value}`);
539
+ }
540
+ }
541
+ previousActiveElement.set(document.activeElement);
542
+ setTimeout(() => {
543
+ const dropdown = dropdownRef();
544
+ if (dropdown) {
545
+ const focusable = getFocusableElements(dropdown);
546
+ if (focusable.length > 0) {
547
+ focusable[0]?.focus();
548
+ }
549
+ }
550
+ }, 10);
551
+ } else {
552
+ const prevEl = previousActiveElement();
553
+ if (prevEl && prevEl.focus) {
554
+ setTimeout(() => prevEl.focus(), 10);
430
555
  }
431
556
  }
432
557
  p.onVisibleChange?.(newOpen);
@@ -444,9 +569,15 @@ var Select = defineComponent({
444
569
  p.onChange?.(Array.from(newSelected));
445
570
  } else {
446
571
  selectedValue.set(/* @__PURE__ */ new Set([option.value]));
447
- isOpen.set(false);
448
- p.onVisibleChange?.(false);
572
+ if (p.closeOnSelect) {
573
+ isOpen.set(false);
574
+ p.onVisibleChange?.(false);
575
+ }
449
576
  p.onChange?.(option.value);
577
+ const prevEl = previousActiveElement();
578
+ if (prevEl && prevEl.focus) {
579
+ setTimeout(() => prevEl.focus(), 10);
580
+ }
450
581
  }
451
582
  };
452
583
  const handleClear = (event) => {
@@ -462,7 +593,9 @@ var Select = defineComponent({
462
593
  switch (event.key) {
463
594
  case "Enter":
464
595
  case " ":
465
- event.preventDefault();
596
+ if (!p.filterable || !open) {
597
+ event.preventDefault();
598
+ }
466
599
  if (open) {
467
600
  if (hIndex >= 0 && hIndex < enabledOptions.length) {
468
601
  const option = enabledOptions[hIndex];
@@ -480,7 +613,12 @@ var Select = defineComponent({
480
613
  toggleDropdown();
481
614
  } else {
482
615
  if (enabledOptions.length > 0) {
483
- highlightedIndex.set(Math.min(hIndex + 1, enabledOptions.length - 1));
616
+ const nextIndex = Math.min(hIndex + 1, enabledOptions.length - 1);
617
+ highlightedIndex.set(nextIndex);
618
+ const option = enabledOptions[nextIndex];
619
+ if (option) {
620
+ activeDescendant.set(`lyt-select-option-${option.value}`);
621
+ }
484
622
  }
485
623
  }
486
624
  break;
@@ -488,7 +626,12 @@ var Select = defineComponent({
488
626
  event.preventDefault();
489
627
  if (open) {
490
628
  if (enabledOptions.length > 0) {
491
- highlightedIndex.set(Math.max(hIndex - 1, 0));
629
+ const prevIndex = Math.max(hIndex - 1, 0);
630
+ highlightedIndex.set(prevIndex);
631
+ const option = enabledOptions[prevIndex];
632
+ if (option) {
633
+ activeDescendant.set(`lyt-select-option-${option.value}`);
634
+ }
492
635
  }
493
636
  }
494
637
  break;
@@ -497,23 +640,64 @@ var Select = defineComponent({
497
640
  event.preventDefault();
498
641
  isOpen.set(false);
499
642
  p.onVisibleChange?.(false);
643
+ const prevEl = previousActiveElement();
644
+ if (prevEl && prevEl.focus) {
645
+ prevEl.focus();
646
+ }
500
647
  }
501
648
  break;
502
649
  case "Home":
503
650
  event.preventDefault();
504
651
  if (open && enabledOptions.length > 0) {
505
652
  highlightedIndex.set(0);
653
+ const option = enabledOptions[0];
654
+ if (option) {
655
+ activeDescendant.set(`lyt-select-option-${option.value}`);
656
+ }
506
657
  }
507
658
  break;
508
659
  case "End":
509
660
  event.preventDefault();
510
661
  if (open && enabledOptions.length > 0) {
511
- highlightedIndex.set(enabledOptions.length - 1);
662
+ const lastIndex = enabledOptions.length - 1;
663
+ highlightedIndex.set(lastIndex);
664
+ const option = enabledOptions[lastIndex];
665
+ if (option) {
666
+ activeDescendant.set(`lyt-select-option-${option.value}`);
667
+ }
668
+ }
669
+ break;
670
+ case "Tab":
671
+ if (open) {
672
+ isOpen.set(false);
673
+ p.onVisibleChange?.(false);
512
674
  }
513
675
  break;
514
676
  }
515
677
  p.onKeydown?.(event);
516
678
  };
679
+ const handleTriggerKeydown = (event) => {
680
+ if (event.key === "ArrowDown" && !isOpen()) {
681
+ event.preventDefault();
682
+ toggleDropdown();
683
+ } else if (event.key === "ArrowUp" && !isOpen()) {
684
+ event.preventDefault();
685
+ toggleDropdown();
686
+ }
687
+ };
688
+ const handleOptionKeydown = (event, option) => {
689
+ if (event.key === "Tab") {
690
+ isOpen.set(false);
691
+ p.onVisibleChange?.(false);
692
+ return;
693
+ }
694
+ if (event.key === "Enter" || event.key === " ") {
695
+ event.preventDefault();
696
+ handleSelect(option);
697
+ return;
698
+ }
699
+ handleKeydown(event);
700
+ };
517
701
  const getSelectedLabel = () => {
518
702
  const selected = Array.from(selectedValue());
519
703
  if (selected.length === 0) return "";
@@ -535,13 +719,19 @@ var Select = defineComponent({
535
719
  const filteredOptions = getFilteredOptions();
536
720
  const selected = selectedValue();
537
721
  const hIndex = highlightedIndex();
722
+ const listId = listboxId();
723
+ const activeId = activeDescendant();
538
724
  const dropdownContent = [];
539
725
  if (filteredOptions.length === 0) {
540
- dropdownContent.push(createVNode("div", { class: "lyt-select__empty" }, [createVNode("span", {}, "\u65E0\u5339\u914D\u9009\u9879")]));
726
+ dropdownContent.push(createVNode("div", {
727
+ class: "lyt-select__empty",
728
+ role: "status"
729
+ }, [createVNode("span", {}, "\u65E0\u5339\u914D\u9009\u9879")]));
541
730
  } else {
542
731
  filteredOptions.forEach((option, index) => {
543
732
  const isSelected = selected.has(option.value);
544
733
  const isHighlighted = !option.disabled && index === hIndex;
734
+ const optionId = `lyt-select-option-${option.value}`;
545
735
  const optionChildren = [];
546
736
  if (isSelected) {
547
737
  optionChildren.push(createVNode("span", { class: "lyt-select__check" }, [createVNode("span", {}, "\u2713")]));
@@ -549,8 +739,10 @@ var Select = defineComponent({
549
739
  optionChildren.push(createVNode("span", {}, option.label));
550
740
  dropdownContent.push(createVNode("div", {
551
741
  key: String(option.value),
742
+ id: optionId,
552
743
  role: "option",
553
744
  "aria-selected": isSelected,
745
+ "aria-disabled": option.disabled,
554
746
  class: [
555
747
  "lyt-select__option",
556
748
  isSelected ? "lyt-select__option--selected" : "",
@@ -559,8 +751,12 @@ var Select = defineComponent({
559
751
  ].filter(Boolean).join(" "),
560
752
  onClick: () => handleSelect(option),
561
753
  onMouseenter: () => {
562
- if (!option.disabled) highlightedIndex.set(index);
563
- }
754
+ if (!option.disabled) {
755
+ highlightedIndex.set(index);
756
+ activeDescendant.set(optionId);
757
+ }
758
+ },
759
+ onKeydown: (e) => handleOptionKeydown(e, option)
564
760
  }, optionChildren));
565
761
  });
566
762
  }
@@ -574,33 +770,65 @@ var Select = defineComponent({
574
770
  if (p.clearable && selected.size > 0) {
575
771
  triggerChildren.push(createVNode("span", {
576
772
  class: "lyt-select__clear",
577
- onClick: handleClear
773
+ onClick: handleClear,
774
+ role: "button",
775
+ "aria-label": "\u6E05\u9664\u9009\u62E9",
776
+ tabIndex: 0
578
777
  }, [createVNode("span", {}, "\xD7")]));
579
778
  }
580
779
  triggerChildren.push(createVNode("span", { class: "lyt-select__arrow" }, [createVNode("span", {}, "\u25BC")]));
581
- const resultChildren = [
582
- createVNode("div", {
583
- class: "lyt-select__trigger",
584
- onClick: toggleDropdown
585
- }, triggerChildren)
586
- ];
780
+ const resultChildren = [];
781
+ if (p.filterable) {
782
+ resultChildren.push(createVNode("input", {
783
+ class: "lyt-select__search",
784
+ type: "text",
785
+ placeholder: "\u641C\u7D22...",
786
+ value: searchValue(),
787
+ onInput: (e) => {
788
+ searchValue.set(e.target.value);
789
+ },
790
+ onKeydown: handleTriggerKeydown,
791
+ "aria-label": "\u641C\u7D22\u9009\u9879",
792
+ "aria-controls": listId
793
+ }));
794
+ }
795
+ resultChildren.push(createVNode("div", {
796
+ ref: (el) => triggerRef.set(el),
797
+ class: "lyt-select__trigger",
798
+ onClick: toggleDropdown,
799
+ onKeydown: handleTriggerKeydown,
800
+ tabIndex: p.disabled ? -1 : p.tabIndex ?? 0,
801
+ role: "combobox",
802
+ "aria-haspopup": "listbox",
803
+ "aria-expanded": open,
804
+ "aria-controls": listId,
805
+ "aria-activedescendant": open ? activeId : void 0
806
+ }, triggerChildren));
587
807
  if (open) {
588
808
  resultChildren.push(createVNode("div", {
809
+ ref: (el) => dropdownRef.set(el),
810
+ id: listId,
589
811
  class: "lyt-select__dropdown",
590
812
  role: "listbox",
591
- "aria-multiselectable": p.multiple
813
+ "aria-multiselectable": p.multiple,
814
+ "aria-label": p.ariaLabel || "\u9009\u62E9\u9009\u9879"
592
815
  }, dropdownContent));
816
+ resultChildren.push(createVNode("div", {
817
+ class: "lyt-select__sro",
818
+ role: "status",
819
+ "aria-live": "polite"
820
+ }, `\u5DF2\u9009\u62E9 ${selected.size} \u4E2A\u9009\u9879`));
593
821
  }
594
822
  const a11yProps = getComboboxA11yProps({
595
823
  id: p.id,
596
- ariaLabel: p.ariaLabel,
824
+ ariaLabel: p.ariaLabel || "\u9009\u62E9\u5668",
597
825
  ariaDescribedBy: p.ariaDescribedBy,
598
826
  ariaRequired: p.ariaRequired,
599
827
  ariaInvalid: p.ariaInvalid,
600
828
  disabled: p.disabled,
601
829
  tabIndex: p.tabIndex,
602
830
  expanded: open,
603
- controls: void 0
831
+ controls: listId
604
832
  });
605
833
  return createVNode("div", mergeA11yProps(a11yProps, {
606
834
  class: selectClass,
@@ -654,6 +882,10 @@ var Tabs = defineComponent({
654
882
  dropIndex: -1
655
883
  });
656
884
  const activeIndex = signal(0);
885
+ const tabListRef = signal(null);
886
+ const previousActiveElement = signal(null);
887
+ const announcement = signal("");
888
+ const tablistId = signal(`lyt-tabs-${Math.random().toString(36).substr(2, 9)}`);
657
889
  const getPanes = () => {
658
890
  const defaultSlot = slots.default;
659
891
  const defaultSlotContent = defaultSlot ? defaultSlot() : [];
@@ -666,11 +898,28 @@ var Tabs = defineComponent({
666
898
  const getEnabledPanes = (panes) => {
667
899
  return panes.filter((pane) => !pane.props.disabled);
668
900
  };
901
+ const announce = (message) => {
902
+ announcement.set(message);
903
+ setTimeout(() => announcement.set(""), 1e3);
904
+ };
669
905
  const handleTabClick = (pane, index) => {
670
906
  if (pane.props.disabled) return;
907
+ const oldIndex = activeIndex();
908
+ activeIndex.set(index);
909
+ if (oldIndex !== index) {
910
+ const panes = getPanes();
911
+ announce(`\u5DF2\u5207\u6362\u5230\u6807\u7B7E\u9875 ${index + 1}\uFF1A${pane.props.label}\uFF0C\u5171 ${panes.length} \u4E2A\u6807\u7B7E\u9875`);
912
+ }
671
913
  p.onChange?.(pane.props.name);
672
914
  p.onTabClick?.(pane, index);
673
- activeIndex.set(index);
915
+ previousActiveElement.set(document.activeElement);
916
+ setTimeout(() => {
917
+ const tabList = tabListRef();
918
+ if (tabList) {
919
+ const activeTab = tabList.querySelector('[role="tab"][aria-selected="true"]');
920
+ activeTab?.focus();
921
+ }
922
+ }, 10);
674
923
  };
675
924
  const handleKeydown = (e) => {
676
925
  const panes = getPanes();
@@ -715,18 +964,30 @@ var Tabs = defineComponent({
715
964
  }
716
965
  break;
717
966
  case "Enter":
718
- case " ":
967
+ case " ": {
719
968
  e.preventDefault();
720
969
  const currentPane = panes[activeIndex()];
721
970
  if (currentPane && !currentPane.props.disabled) {
722
971
  handleTabClick(currentPane, activeIndex());
723
972
  }
724
973
  break;
974
+ }
975
+ case "Delete":
976
+ case "Backspace":
977
+ if (p.closable) {
978
+ const currentPane = panes[activeIndex()];
979
+ if (currentPane && currentPane.props.closable) {
980
+ e.preventDefault();
981
+ handleTabRemove(currentPane, new Event("keydown"));
982
+ }
983
+ }
984
+ break;
725
985
  }
726
986
  if (newIndex !== activeIndex()) {
727
- activeIndex.set(newIndex);
728
987
  const pane = panes[newIndex];
729
988
  if (pane && !pane.props.disabled) {
989
+ activeIndex.set(newIndex);
990
+ announce(`\u5DF2\u5207\u6362\u5230\u6807\u7B7E\u9875 ${newIndex + 1}\uFF1A${pane.props.label}`);
730
991
  handleTabClick(pane, newIndex);
731
992
  }
732
993
  }
@@ -734,9 +995,27 @@ var Tabs = defineComponent({
734
995
  };
735
996
  const handleTabRemove = (pane, e) => {
736
997
  e.stopPropagation();
998
+ const panes = getPanes();
999
+ const index = panes.findIndex((p2) => p2.props.name === pane.props.name);
1000
+ if (index > 0) {
1001
+ const prevPane = panes[index - 1];
1002
+ activeIndex.set(index - 1);
1003
+ if (prevPane) {
1004
+ announce(`\u5DF2\u5173\u95ED\u6807\u7B7E\u9875 ${pane.props.label}\uFF0C\u73B0\u5728\u663E\u793A\u6807\u7B7E\u9875 ${prevPane.props.label}`);
1005
+ }
1006
+ } else if (panes.length > 1) {
1007
+ const nextPane = panes[index + 1];
1008
+ activeIndex.set(0);
1009
+ if (nextPane) {
1010
+ announce(`\u5DF2\u5173\u95ED\u6807\u7B7E\u9875 ${pane.props.label}\uFF0C\u73B0\u5728\u663E\u793A\u6807\u7B7E\u9875 ${nextPane.props.label}`);
1011
+ }
1012
+ } else {
1013
+ announce(`\u5DF2\u5173\u95ED\u6807\u7B7E\u9875 ${pane.props.label}\uFF0C\u73B0\u5728\u6CA1\u6709\u6807\u7B7E\u9875`);
1014
+ }
737
1015
  p.onTabRemove?.(pane.props.name);
738
1016
  };
739
1017
  const handleTabAdd = () => {
1018
+ announce("\u6B63\u5728\u6DFB\u52A0\u65B0\u6807\u7B7E\u9875");
740
1019
  p.onTabAdd?.();
741
1020
  };
742
1021
  const handleDragStart = (index) => {
@@ -758,6 +1037,7 @@ var Tabs = defineComponent({
758
1037
  const handleDragEnd = () => {
759
1038
  const { dragIndex, dropIndex } = dragState();
760
1039
  if (dragIndex !== -1 && dropIndex !== -1 && dragIndex !== dropIndex) {
1040
+ announce(`\u6807\u7B7E\u9875\u5DF2\u4ECE\u4F4D\u7F6E ${dragIndex + 1} \u79FB\u52A8\u5230\u4F4D\u7F6E ${dropIndex + 1}`);
761
1041
  p.onTabDragEnd?.(dragIndex, dropIndex);
762
1042
  }
763
1043
  dragState.set({
@@ -778,6 +1058,7 @@ var Tabs = defineComponent({
778
1058
  `lyt-tabs--${p.type || "normal"}`,
779
1059
  p.class
780
1060
  ].filter(Boolean).join(" ");
1061
+ const tabListId = tablistId();
781
1062
  const tabItems = panes.map((pane, index) => {
782
1063
  const isActive = pane.props.name === currentName;
783
1064
  const isDraggable = p.draggable;
@@ -786,7 +1067,7 @@ var Tabs = defineComponent({
786
1067
  ];
787
1068
  if (pane.props.closable || p.closable) {
788
1069
  const closeBtnProps = getButtonA11yProps({
789
- ariaLabel: "Close tab",
1070
+ ariaLabel: `\u5173\u95ED\u6807\u7B7E\u9875 ${pane.props.label}`,
790
1071
  disabled: pane.props.disabled
791
1072
  });
792
1073
  tabChildren.push(createVNode("span", mergeA11yProps(closeBtnProps, {
@@ -794,11 +1075,13 @@ var Tabs = defineComponent({
794
1075
  onClick: (e) => handleTabRemove(pane, e)
795
1076
  }), [createTextVNode("\xD7")]));
796
1077
  }
1078
+ const tabId = `${p.id || tabListId}-tab-${pane.props.name}`;
1079
+ const panelId2 = `${p.id || tabListId}-panel-${pane.props.name}`;
797
1080
  const tabA11yProps = getTabA11yProps({
798
- id: `${p.id || "tabs"}-tab-${pane.props.name}`,
1081
+ id: tabId,
799
1082
  selected: isActive,
800
1083
  disabled: pane.props.disabled,
801
- controls: `${p.id || "tabs"}-panel-${pane.props.name}`
1084
+ controls: panelId2
802
1085
  });
803
1086
  return createVNode("div", mergeA11yProps(tabA11yProps, {
804
1087
  key: String(pane.props.name),
@@ -821,7 +1104,7 @@ var Tabs = defineComponent({
821
1104
  });
822
1105
  if (p.addable || p.editable) {
823
1106
  const addBtnProps = getButtonA11yProps({
824
- ariaLabel: "Add tab"
1107
+ ariaLabel: "\u6DFB\u52A0\u65B0\u6807\u7B7E\u9875"
825
1108
  });
826
1109
  tabItems.push(createVNode("div", mergeA11yProps(addBtnProps, {
827
1110
  class: "lyt-tabs__add-btn",
@@ -829,26 +1112,40 @@ var Tabs = defineComponent({
829
1112
  }), [createTextVNode("+")]));
830
1113
  }
831
1114
  const content = panes.find((pane) => pane.props.name === currentName);
1115
+ const panelId = `${p.id || tabListId}-panel-${content?.props.name || "default"}`;
832
1116
  const tabpanelProps = content ? getTabpanelA11yProps({
833
- id: `${p.id || "tabs"}-panel-${content.props.name}`,
834
- labelledBy: `${p.id || "tabs"}-tab-${content.props.name}`
1117
+ id: panelId,
1118
+ labelledBy: `${p.id || tabListId}-tab-${content.props.name}`
835
1119
  }) : {};
836
1120
  const contentPane = content ? createVNode("div", mergeA11yProps(tabpanelProps, {
837
- class: "lyt-tabs__pane"
838
- }), content.children) : createVNode("div", { style: "display: none;" }, []);
1121
+ class: "lyt-tabs__pane",
1122
+ id: panelId,
1123
+ role: "tabpanel"
1124
+ }), content.children) : createVNode("div", { style: "display: none;", id: panelId }, []);
839
1125
  const tablistA11yProps = getTablistA11yProps({
840
- id: p.id,
841
- ariaLabel: p.ariaLabel || "Tabs",
1126
+ id: p.id || tabListId,
1127
+ ariaLabel: p.ariaLabel || "\u6807\u7B7E\u9875\u5BFC\u822A",
842
1128
  ariaDescribedBy: p.ariaDescribedBy
843
1129
  });
844
1130
  return createVNode("div", mergeA11yProps(tablistA11yProps, {
845
1131
  class: tabsClass,
846
1132
  onKeydown: handleKeydown
847
1133
  }), [
848
- createVNode("div", { class: "lyt-tabs__header" }, [
1134
+ createVNode("div", {
1135
+ ref: (el) => tabListRef.set(el),
1136
+ class: "lyt-tabs__header",
1137
+ role: "tablist",
1138
+ "aria-label": p.ariaLabel || "\u6807\u7B7E\u9875"
1139
+ }, [
849
1140
  createVNode("div", { class: "lyt-tabs__nav" }, tabItems)
850
1141
  ]),
851
- createVNode("div", { class: "lyt-tabs__content" }, [contentPane])
1142
+ createVNode("div", { class: "lyt-tabs__content" }, [contentPane]),
1143
+ createVNode("div", {
1144
+ class: "lyt-tabs__sro",
1145
+ role: "status",
1146
+ "aria-live": "polite",
1147
+ "aria-atomic": "true"
1148
+ }, announcement())
852
1149
  ]);
853
1150
  };
854
1151
  }
@@ -1021,6 +1318,22 @@ var Table = defineComponent({
1021
1318
  };
1022
1319
  }
1023
1320
  });
1321
+ var DEFAULT_LOCALE = {
1322
+ required: "\u6B64\u5B57\u6BB5\u5FC5\u586B",
1323
+ pattern: "\u683C\u5F0F\u4E0D\u6B63\u786E",
1324
+ min: "\u957F\u5EA6\u4E0D\u80FD\u5C11\u4E8E {min} \u4E2A\u5B57\u7B26",
1325
+ max: "\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 {max} \u4E2A\u5B57\u7B26",
1326
+ type: "\u7C7B\u578B\u4E0D\u6B63\u786E",
1327
+ validator: "\u9A8C\u8BC1\u5931\u8D25",
1328
+ default: "\u9A8C\u8BC1\u5931\u8D25"
1329
+ };
1330
+ function formatMessage(template, params) {
1331
+ let message = template;
1332
+ for (const [key, value] of Object.entries(params)) {
1333
+ message = message.replace(new RegExp(`\\{${key}\\}`, "g"), String(value));
1334
+ }
1335
+ return message;
1336
+ }
1024
1337
  var Form = defineComponent({
1025
1338
  name: "LytForm",
1026
1339
  props: {
@@ -1028,6 +1341,7 @@ var Form = defineComponent({
1028
1341
  rules: { type: Object, default: () => ({}) },
1029
1342
  labelWidth: { type: String, default: "100px" },
1030
1343
  labelPosition: { type: String, default: "right" },
1344
+ locale: { type: Object, default: () => DEFAULT_LOCALE },
1031
1345
  class: { type: String, default: "" },
1032
1346
  id: { type: String, default: "" },
1033
1347
  ariaLabel: { type: String, default: "" },
@@ -1037,44 +1351,81 @@ var Form = defineComponent({
1037
1351
  setup(props, { slots }) {
1038
1352
  const p = props;
1039
1353
  const errors = signal({});
1040
- const validateField = (field) => {
1354
+ const validating = signal({});
1355
+ const locale = p.locale || DEFAULT_LOCALE;
1356
+ const validateField = async (field) => {
1041
1357
  const rules = p.rules[field];
1042
1358
  if (!rules || rules.length === 0) return true;
1043
1359
  const value = p.model[field];
1360
+ const fieldErrors = [];
1044
1361
  for (const rule of rules) {
1045
1362
  if (rule.required && (!value || value === "")) {
1046
- errors.set({ ...errors(), [field]: rule.message || "\u6B64\u5B57\u6BB5\u5FC5\u586B" });
1047
- return false;
1048
- }
1049
- if (rule.pattern && !rule.pattern.test(String(value || ""))) {
1050
- errors.set({ ...errors(), [field]: rule.message || "\u683C\u5F0F\u4E0D\u6B63\u786E" });
1051
- return false;
1363
+ const message = rule.message || formatMessage(locale.required, { field });
1364
+ fieldErrors.push(message);
1365
+ continue;
1366
+ }
1367
+ if (value !== void 0 && value !== null && value !== "") {
1368
+ if (rule.type) {
1369
+ const typeValid = validateType(value, rule.type);
1370
+ if (!typeValid) {
1371
+ fieldErrors.push(rule.message || formatMessage(locale.type, { type: rule.type, field }));
1372
+ continue;
1373
+ }
1374
+ }
1375
+ if (rule.pattern && !rule.pattern.test(String(value))) {
1376
+ fieldErrors.push(rule.message || formatMessage(locale.pattern, { field }));
1377
+ continue;
1378
+ }
1379
+ if (typeof value === "string") {
1380
+ if (rule.min !== void 0 && value.length < rule.min) {
1381
+ fieldErrors.push(rule.message || formatMessage(locale.min, { min: rule.min, field }));
1382
+ continue;
1383
+ }
1384
+ if (rule.max !== void 0 && value.length > rule.max) {
1385
+ fieldErrors.push(rule.message || formatMessage(locale.max, { max: rule.max, field }));
1386
+ continue;
1387
+ }
1388
+ }
1052
1389
  }
1053
1390
  if (rule.validator) {
1054
- const result = rule.validator(value, p.model);
1055
- if (result !== true) {
1056
- errors.set({ ...errors(), [field]: typeof result === "string" ? result : "\u9A8C\u8BC1\u5931\u8D25" });
1057
- return false;
1391
+ validating.set({ ...validating(), [field]: true });
1392
+ try {
1393
+ const result = await Promise.resolve(rule.validator(value, p.model));
1394
+ if (result !== true) {
1395
+ const errorMessage = typeof result === "string" ? result : formatMessage(locale.validator, { field });
1396
+ fieldErrors.push(errorMessage);
1397
+ }
1398
+ } catch (_error) {
1399
+ fieldErrors.push(rule.message || formatMessage(locale.validator, { field }));
1400
+ } finally {
1401
+ validating.set({ ...validating(), [field]: false });
1058
1402
  }
1059
1403
  }
1060
1404
  }
1405
+ if (fieldErrors.length > 0) {
1406
+ errors.set({ ...errors(), [field]: fieldErrors[0] });
1407
+ return false;
1408
+ }
1061
1409
  const newErrors = { ...errors() };
1062
1410
  delete newErrors[field];
1063
1411
  errors.set(newErrors);
1064
1412
  return true;
1065
1413
  };
1066
- const validate = () => {
1414
+ const validate = async () => {
1067
1415
  let isValid = true;
1068
- for (const field in p.rules) {
1069
- if (!validateField(field)) {
1416
+ const fieldNames = Object.keys(p.rules);
1417
+ for (const field of fieldNames) {
1418
+ const fieldValid = await validateField(field);
1419
+ if (!fieldValid) {
1070
1420
  isValid = false;
1071
1421
  }
1072
1422
  }
1073
1423
  return isValid;
1074
1424
  };
1075
- const handleSubmit = (event) => {
1425
+ const handleSubmit = async (event) => {
1076
1426
  event.preventDefault();
1077
- if (validate()) {
1427
+ const isValid = await validate();
1428
+ if (isValid) {
1078
1429
  p.onSubmit?.(p.model);
1079
1430
  }
1080
1431
  };
@@ -1095,6 +1446,33 @@ var Form = defineComponent({
1095
1446
  };
1096
1447
  }
1097
1448
  });
1449
+ function validateType(value, type) {
1450
+ switch (type) {
1451
+ case "string":
1452
+ return typeof value === "string";
1453
+ case "number":
1454
+ return typeof value === "number" && !isNaN(value);
1455
+ case "boolean":
1456
+ return typeof value === "boolean";
1457
+ case "array":
1458
+ return Array.isArray(value);
1459
+ case "date":
1460
+ return value instanceof Date || !isNaN(Date.parse(String(value)));
1461
+ case "email": {
1462
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1463
+ return typeof value === "string" && emailRegex.test(value);
1464
+ }
1465
+ case "url":
1466
+ try {
1467
+ new URL(String(value));
1468
+ return true;
1469
+ } catch {
1470
+ return false;
1471
+ }
1472
+ default:
1473
+ return true;
1474
+ }
1475
+ }
1098
1476
  var FormItem = defineComponent({
1099
1477
  name: "LytFormItem",
1100
1478
  props: {
@@ -1153,15 +1531,16 @@ var Transition = defineComponent({
1153
1531
  class: { type: String, default: "" }
1154
1532
  },
1155
1533
  setup(props, { slots }) {
1534
+ const p = props;
1156
1535
  const getTransitionClass = () => {
1157
- const classes = [`${props.name}-transition`];
1158
- if (props.class) {
1159
- classes.push(props.class);
1536
+ const classes = [`${p.name}-transition`];
1537
+ if (p.class) {
1538
+ classes.push(p.class);
1160
1539
  }
1161
1540
  return classes.join(" ");
1162
1541
  };
1163
1542
  const getTransitionStyle = () => {
1164
- return `transition-duration: ${props.duration}ms;`;
1543
+ return `transition-duration: ${p.duration}ms;`;
1165
1544
  };
1166
1545
  return () => {
1167
1546
  return createVNode("div", {
@@ -1180,17 +1559,18 @@ var TransitionGroup = defineComponent({
1180
1559
  class: { type: String, default: "" }
1181
1560
  },
1182
1561
  setup(props, { slots }) {
1562
+ const p = props;
1183
1563
  return () => {
1184
1564
  const children = slots.default?.() || [];
1185
1565
  const wrappedChildren = children.map(
1186
1566
  (child, index) => createVNode("div", {
1187
1567
  key: child.key || index,
1188
- class: `${props.name}-item`,
1189
- style: `transition: all ${props.duration}ms ease;`
1568
+ class: `${p.name}-item`,
1569
+ style: `transition: all ${p.duration}ms ease;`
1190
1570
  }, [child])
1191
1571
  );
1192
- return createVNode(props.tag, {
1193
- class: `lyt-transition-group ${props.class}`
1572
+ return createVNode(p.tag, {
1573
+ class: `lyt-transition-group ${p.class}`
1194
1574
  }, wrappedChildren);
1195
1575
  };
1196
1576
  }
@@ -1657,24 +2037,167 @@ var Menu = defineComponent({
1657
2037
  const p = props;
1658
2038
  const activeIndex = signal(p.defaultActive);
1659
2039
  const openedIndexes = signal(new Set(p.defaultOpeneds));
2040
+ const menuRef = signal(null);
2041
+ const currentSubmenu = signal(null);
2042
+ const focusedIndex = signal(-1);
2043
+ const announcement = signal("");
2044
+ const previousActiveElement = signal(null);
2045
+ const menuId = signal(`lyt-menu-${Math.random().toString(36).substr(2, 9)}`);
2046
+ const announce = (message) => {
2047
+ announcement.set(message);
2048
+ setTimeout(() => announcement.set(""), 1e3);
2049
+ };
2050
+ const getMenuItems = () => {
2051
+ const items = slots.default?.() || [];
2052
+ const menuItems = [];
2053
+ items.forEach((item) => {
2054
+ const itemProps = item.props;
2055
+ if (!itemProps || !itemProps.index) return;
2056
+ const hasChildren = !!(itemProps.children && itemProps.children.length > 0);
2057
+ menuItems.push({
2058
+ index: itemProps.index,
2059
+ label: itemProps.label || "",
2060
+ disabled: !!itemProps.disabled,
2061
+ hasChildren,
2062
+ element: null
2063
+ });
2064
+ });
2065
+ return menuItems;
2066
+ };
1660
2067
  const handleSelect = (index) => {
1661
2068
  activeIndex.set(index);
2069
+ announce(`\u5DF2\u9009\u62E9\u83DC\u5355\u9879\uFF1A${index}`);
1662
2070
  p.onSelect?.(index);
1663
2071
  };
1664
- const toggleSubmenu = (index) => {
2072
+ const toggleSubmenu = (index, itemLabel) => {
1665
2073
  const newOpened = new Set(openedIndexes());
1666
2074
  if (newOpened.has(index)) {
1667
2075
  newOpened.delete(index);
2076
+ announce(`\u5DF2\u5173\u95ED\u5B50\u83DC\u5355\uFF1A${itemLabel}`);
1668
2077
  p.onClose?.(index);
2078
+ currentSubmenu.set(null);
2079
+ const prevEl = previousActiveElement();
2080
+ if (prevEl && prevEl.focus) {
2081
+ prevEl.focus();
2082
+ }
1669
2083
  } else {
1670
2084
  if (p.uniqueOpened) {
1671
2085
  newOpened.clear();
2086
+ if (currentSubmenu()) {
2087
+ announce("\u5DF2\u5173\u95ED\u5176\u4ED6\u5B50\u83DC\u5355");
2088
+ }
1672
2089
  }
1673
2090
  newOpened.add(index);
2091
+ announce(`\u5DF2\u6253\u5F00\u5B50\u83DC\u5355\uFF1A${itemLabel}`);
1674
2092
  p.onOpen?.(index);
2093
+ currentSubmenu.set(index);
2094
+ previousActiveElement.set(document.activeElement);
2095
+ setTimeout(() => {
2096
+ const menu = menuRef();
2097
+ if (menu) {
2098
+ const submenu = menu.querySelector(`[data-submenu-id="${index}"] ul`);
2099
+ if (submenu) {
2100
+ const firstItem = submenu.querySelector('[role="menuitem"]');
2101
+ firstItem?.focus();
2102
+ }
2103
+ }
2104
+ }, 10);
1675
2105
  }
1676
2106
  openedIndexes.set(newOpened);
1677
2107
  };
2108
+ const handleKeydown = (e, itemIndex, hasChildren, itemLabel) => {
2109
+ const menuItems = getMenuItems();
2110
+ const currentIndex = menuItems.findIndex((item) => item.index === itemIndex);
2111
+ switch (e.key) {
2112
+ case "ArrowDown":
2113
+ e.preventDefault();
2114
+ if (hasChildren && !openedIndexes().has(itemIndex)) {
2115
+ toggleSubmenu(itemIndex, itemLabel);
2116
+ } else {
2117
+ for (let i = 1; i <= menuItems.length; i++) {
2118
+ const nextIndex = (currentIndex + i) % menuItems.length;
2119
+ const nextItem = menuItems[nextIndex];
2120
+ if (nextItem && !nextItem.disabled) {
2121
+ announce(`\u5BFC\u822A\u5230\u83DC\u5355\u9879\uFF1A${nextItem.label}`);
2122
+ focusedIndex.set(nextIndex);
2123
+ break;
2124
+ }
2125
+ }
2126
+ }
2127
+ break;
2128
+ case "ArrowUp":
2129
+ e.preventDefault();
2130
+ for (let i = 1; i <= menuItems.length; i++) {
2131
+ const prevIndex = (currentIndex - i + menuItems.length) % menuItems.length;
2132
+ const prevItem = menuItems[prevIndex];
2133
+ if (prevItem && !prevItem.disabled) {
2134
+ announce(`\u5BFC\u822A\u5230\u83DC\u5355\u9879\uFF1A${prevItem.label}`);
2135
+ focusedIndex.set(prevIndex);
2136
+ break;
2137
+ }
2138
+ }
2139
+ break;
2140
+ case "ArrowRight":
2141
+ e.preventDefault();
2142
+ for (let i = 1; i <= menuItems.length; i++) {
2143
+ const nextIndex = (currentIndex + i) % menuItems.length;
2144
+ const nextItem = menuItems[nextIndex];
2145
+ if (nextItem && !nextItem.disabled) {
2146
+ announce(`\u5BFC\u822A\u5230\u83DC\u5355\u9879\uFF1A${nextItem.label}`);
2147
+ focusedIndex.set(nextIndex);
2148
+ break;
2149
+ }
2150
+ }
2151
+ break;
2152
+ case "ArrowLeft":
2153
+ e.preventDefault();
2154
+ for (let i = 1; i <= menuItems.length; i++) {
2155
+ const prevIndex = (currentIndex - i + menuItems.length) % menuItems.length;
2156
+ const prevItem = menuItems[prevIndex];
2157
+ if (prevItem && !prevItem.disabled) {
2158
+ announce(`\u5BFC\u822A\u5230\u83DC\u5355\u9879\uFF1A${prevItem.label}`);
2159
+ focusedIndex.set(prevIndex);
2160
+ break;
2161
+ }
2162
+ }
2163
+ break;
2164
+ case "Enter":
2165
+ case " ":
2166
+ e.preventDefault();
2167
+ if (hasChildren) {
2168
+ toggleSubmenu(itemIndex, itemLabel);
2169
+ } else {
2170
+ handleSelect(itemIndex);
2171
+ }
2172
+ break;
2173
+ case "Escape":
2174
+ if (openedIndexes().has(itemIndex)) {
2175
+ e.preventDefault();
2176
+ toggleSubmenu(itemIndex, itemLabel);
2177
+ }
2178
+ break;
2179
+ case "Home": {
2180
+ e.preventDefault();
2181
+ const firstEnabled = menuItems.find((item) => !item.disabled);
2182
+ if (firstEnabled) {
2183
+ announce(`\u5BFC\u822A\u5230\u7B2C\u4E00\u4E2A\u83DC\u5355\u9879\uFF1A${firstEnabled.label}`);
2184
+ focusedIndex.set(menuItems.indexOf(firstEnabled));
2185
+ }
2186
+ break;
2187
+ }
2188
+ case "End":
2189
+ e.preventDefault();
2190
+ for (let i = menuItems.length - 1; i >= 0; i--) {
2191
+ const item = menuItems[i];
2192
+ if (item && !item.disabled) {
2193
+ announce(`\u5BFC\u822A\u5230\u6700\u540E\u4E00\u4E2A\u83DC\u5355\u9879\uFF1A${item.label}`);
2194
+ focusedIndex.set(i);
2195
+ break;
2196
+ }
2197
+ }
2198
+ break;
2199
+ }
2200
+ };
1678
2201
  return () => {
1679
2202
  const menuClass = [
1680
2203
  "lyt-menu",
@@ -1682,66 +2205,105 @@ var Menu = defineComponent({
1682
2205
  p.class
1683
2206
  ].filter(Boolean).join(" ");
1684
2207
  const items = slots.default?.() || [];
1685
- const menuItems = items.map((item) => {
2208
+ const menuListId = menuId();
2209
+ const menuItems = items.map((item, index) => {
1686
2210
  const itemProps = item.props;
1687
2211
  if (!itemProps) return item;
1688
2212
  const isActive = itemProps.index === activeIndex();
1689
2213
  const isOpened = openedIndexes().has(itemProps.index);
2214
+ const isFocused = focusedIndex() === index;
1690
2215
  const itemChildren = [];
1691
2216
  if (itemProps.icon) {
1692
2217
  itemChildren.push(createVNode("span", { class: "lyt-menu__icon" }, [itemProps.icon]));
1693
2218
  }
1694
2219
  itemChildren.push(createVNode("span", { class: "lyt-menu__title" }, [createVNode("span", {}, String(itemProps.label || ""))]));
1695
- if (itemProps.children && itemProps.children.length > 0) {
2220
+ const hasChildren = !!(itemProps.children && itemProps.children.length > 0);
2221
+ const itemId = `${menuListId}-item-${itemProps.index}`;
2222
+ if (hasChildren) {
1696
2223
  itemChildren.push(createVNode("span", {
1697
- class: ["lyt-menu__arrow", isOpened ? "lyt-menu__arrow--opened" : ""].filter(Boolean).join(" ")
2224
+ class: ["lyt-menu__arrow", isOpened ? "lyt-menu__arrow--opened" : ""].filter(Boolean).join(" "),
2225
+ "aria-hidden": "true"
1698
2226
  }, [createVNode("span", {}, isOpened ? "\u25B2" : "\u25BC")]));
1699
2227
  const submenuChildren = itemProps.children.map((child) => {
1700
2228
  const childItem = child;
1701
- const childBtnProps = getButtonA11yProps({
1702
- ariaLabel: childItem.label,
1703
- disabled: childItem.disabled
1704
- });
1705
- return createVNode("li", mergeA11yProps(childBtnProps, {
2229
+ const childItemId = `${itemId}-child-${childItem.index}`;
2230
+ const isChildActive = childItem.index === activeIndex();
2231
+ return createVNode("li", {
1706
2232
  key: childItem.index,
2233
+ id: childItemId,
1707
2234
  class: [
1708
2235
  "lyt-menu__item",
1709
- childItem.index === activeIndex() ? "lyt-menu__item--active" : "",
2236
+ "lyt-menu__submenu-item",
2237
+ isChildActive ? "lyt-menu__item--active" : "",
1710
2238
  childItem.disabled ? "lyt-menu__item--disabled" : ""
1711
2239
  ].filter(Boolean).join(" "),
2240
+ role: "menuitem",
2241
+ "aria-disabled": childItem.disabled,
1712
2242
  onClick: () => {
1713
2243
  if (childItem.disabled) return;
1714
2244
  handleSelect(childItem.index);
1715
2245
  }
1716
- }), [
2246
+ }, [
1717
2247
  childItem.icon ? createVNode("span", { class: "lyt-menu__icon" }, [childItem.icon]) : createVNode("span", {}, ""),
1718
2248
  createVNode("span", { class: "lyt-menu__title" }, [createVNode("span", {}, String(childItem.label || ""))])
1719
2249
  ]);
1720
2250
  });
1721
- itemChildren.push(createVNode("ul", { class: "lyt-menu__submenu" }, submenuChildren));
2251
+ itemChildren.push(createVNode("div", {
2252
+ "data-submenu-id": itemProps.index,
2253
+ class: ["lyt-menu__submenu", isOpened ? "lyt-menu__submenu--opened" : ""].filter(Boolean).join(" "),
2254
+ "aria-label": itemProps.label
2255
+ }, [
2256
+ createVNode("ul", {
2257
+ class: "lyt-menu__submenu-list",
2258
+ role: "menu"
2259
+ }, submenuChildren)
2260
+ ]));
1722
2261
  }
1723
- const itemBtnProps = getButtonA11yProps({
1724
- ariaLabel: itemProps.label,
1725
- disabled: itemProps.disabled
1726
- });
1727
- return createVNode("li", mergeA11yProps(itemBtnProps, {
2262
+ const itemMenuitemProps = {
2263
+ id: itemId,
2264
+ "aria-disabled": itemProps.disabled ? true : void 0,
2265
+ "aria-expanded": hasChildren ? isOpened : void 0,
2266
+ "aria-haspopup": hasChildren ? true : void 0
2267
+ };
2268
+ return createVNode("li", mergeA11yProps(itemMenuitemProps, {
1728
2269
  key: itemProps.index,
1729
2270
  class: [
1730
2271
  "lyt-menu__item",
1731
2272
  isActive ? "lyt-menu__item--active" : "",
2273
+ isFocused ? "lyt-menu__item--focused" : "",
1732
2274
  itemProps.disabled ? "lyt-menu__item--disabled" : ""
1733
2275
  ].filter(Boolean).join(" "),
2276
+ role: "menuitem",
2277
+ tabIndex: isFocused ? 0 : -1,
2278
+ "aria-haspopup": hasChildren,
2279
+ "aria-expanded": hasChildren ? isOpened : void 0,
1734
2280
  onClick: () => {
1735
2281
  if (itemProps.disabled) return;
1736
- if (itemProps.children && itemProps.children.length > 0) {
1737
- toggleSubmenu(itemProps.index);
2282
+ if (hasChildren) {
2283
+ toggleSubmenu(itemProps.index, itemProps.label || "");
1738
2284
  } else {
1739
2285
  handleSelect(itemProps.index);
1740
2286
  }
1741
- }
2287
+ },
2288
+ onKeydown: (e) => handleKeydown(e, itemProps.index, hasChildren, itemProps.label || "")
1742
2289
  }), itemChildren);
1743
2290
  });
1744
- return createVNode("ul", { class: menuClass, role: "menubar", id: p.id, "aria-label": p.ariaLabel }, menuItems);
2291
+ return createVNode("div", {
2292
+ ref: (el) => menuRef.set(el)
2293
+ }, [
2294
+ createVNode("ul", {
2295
+ class: menuClass,
2296
+ role: "menubar",
2297
+ id: p.id || menuListId,
2298
+ "aria-label": p.ariaLabel || "\u4E3B\u83DC\u5355"
2299
+ }, menuItems),
2300
+ createVNode("div", {
2301
+ class: "lyt-menu__sro",
2302
+ role: "status",
2303
+ "aria-live": "polite",
2304
+ "aria-atomic": "true"
2305
+ }, announcement())
2306
+ ]);
1745
2307
  };
1746
2308
  }
1747
2309
  });
@@ -1811,7 +2373,7 @@ var Cascader = defineComponent({
1811
2373
  const newPath = [...activePath().slice(0, level), option.value];
1812
2374
  activePath.set(newPath);
1813
2375
  const nextOptions = option.children || [];
1814
- if (option.isLeaf || nextOptions.length === 0) {
2376
+ if (option.isLeaf) {
1815
2377
  if (p.multiple) {
1816
2378
  const exists = selectedValues().some((item) => item.join("-") === newPath.join("-"));
1817
2379
  const newSelected = exists ? selectedValues().filter((item) => item.join("-") !== newPath.join("-")) : [...selectedValues(), newPath];
@@ -1822,7 +2384,7 @@ var Cascader = defineComponent({
1822
2384
  isDropdownOpen.set(false);
1823
2385
  p.onChange?.(newPath);
1824
2386
  }
1825
- } else if (p.load && nextOptions.length === 0) {
2387
+ } else if (nextOptions.length === 0 && p.load) {
1826
2388
  option.loading = true;
1827
2389
  p.load(option, (children) => {
1828
2390
  option.loading = false;
@@ -3899,23 +4461,24 @@ var Icon = defineComponent({
3899
4461
  ariaDescribedBy: { type: String, default: "" }
3900
4462
  },
3901
4463
  setup(props, { slots }) {
4464
+ const p = props;
3902
4465
  const getIconClass = () => {
3903
4466
  const classes = ["lyt-icon"];
3904
- if (props.name) classes.push(`lyt-icon--${props.name}`);
3905
- if (props.spin) classes.push("lyt-icon--spin");
3906
- if (props.class) classes.push(props.class);
4467
+ if (p.name) classes.push(`lyt-icon--${p.name}`);
4468
+ if (p.spin) classes.push("lyt-icon--spin");
4469
+ if (p.class) classes.push(p.class);
3907
4470
  return classes.join(" ");
3908
4471
  };
3909
4472
  const getIconStyle = () => {
3910
4473
  const style = {};
3911
- if (props.size) style.fontSize = props.size;
3912
- if (props.color) style.color = props.color;
3913
- if (props.style) {
3914
- if (isString(props.style)) {
3915
- return props.style;
4474
+ if (p.size) style.fontSize = p.size;
4475
+ if (p.color) style.color = p.color;
4476
+ if (p.style) {
4477
+ if (isString(p.style)) {
4478
+ return p.style;
3916
4479
  }
3917
- if (isObject(props.style)) {
3918
- Object.assign(style, props.style);
4480
+ if (isObject(p.style)) {
4481
+ Object.assign(style, p.style);
3919
4482
  }
3920
4483
  }
3921
4484
  return style;
@@ -3926,9 +4489,9 @@ var Icon = defineComponent({
3926
4489
  children.push(...slots.default());
3927
4490
  }
3928
4491
  return createVNode("i", mergeA11yProps({
3929
- id: props.id,
3930
- "aria-label": props.ariaLabel,
3931
- "aria-describedby": props.ariaDescribedBy
4492
+ id: p.id,
4493
+ "aria-label": p.ariaLabel,
4494
+ "aria-describedby": p.ariaDescribedBy
3932
4495
  }, {
3933
4496
  class: getIconClass(),
3934
4497
  style: getIconStyle()
@@ -4090,6 +4653,7 @@ var Spin = defineComponent({
4090
4653
  ariaDescribedBy: { type: String, default: "" }
4091
4654
  },
4092
4655
  setup(props, { slots }) {
4656
+ const p = props;
4093
4657
  const state = reactive({
4094
4658
  visible: props.spinning
4095
4659
  });
@@ -4097,10 +4661,10 @@ var Spin = defineComponent({
4097
4661
  watch(() => props.spinning, (val) => {
4098
4662
  if (delayTimer) clearTimeout(delayTimer);
4099
4663
  if (val) {
4100
- if (props.delay > 0) {
4664
+ if (p.delay && p.delay > 0) {
4101
4665
  delayTimer = setTimeout(() => {
4102
4666
  state.visible = true;
4103
- }, props.delay);
4667
+ }, p.delay);
4104
4668
  } else {
4105
4669
  state.visible = true;
4106
4670
  }
@@ -4114,14 +4678,14 @@ var Spin = defineComponent({
4114
4678
  const getSpinClass = () => {
4115
4679
  const classes = ["lyt-spin"];
4116
4680
  if (state.visible) classes.push("lyt-spin--spinning");
4117
- if (props.class) classes.push(props.class);
4681
+ if (p.class) classes.push(p.class);
4118
4682
  return classes.join(" ");
4119
4683
  };
4120
4684
  const getSpinStyle = () => {
4121
- if (!props.style) return void 0;
4122
- if (isString(props.style)) return props.style;
4123
- if (isObject(props.style)) {
4124
- return Object.entries(props.style).map(([key, value]) => `${key}: ${value}`).join("; ");
4685
+ if (!p.style) return void 0;
4686
+ if (isString(p.style)) return p.style;
4687
+ if (isObject(p.style)) {
4688
+ return Object.entries(p.style).map(([key, value]) => `${key}: ${value}`).join("; ");
4125
4689
  }
4126
4690
  return void 0;
4127
4691
  };
@@ -4129,15 +4693,15 @@ var Spin = defineComponent({
4129
4693
  const children = [];
4130
4694
  if (state.visible) {
4131
4695
  const loadingChildren = [];
4132
- loadingChildren.push(createVNode("div", { class: `lyt-spin__icon lyt-spin__icon--${props.size}` }, [
4696
+ loadingChildren.push(createVNode("div", { class: `lyt-spin__icon lyt-spin__icon--${p.size}` }, [
4133
4697
  createVNode("svg", { viewBox: "0 0 1024 1024", class: "lyt-spin__svg" }, [
4134
4698
  createVNode("path", {
4135
4699
  d: "M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32zm0 640a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V736a32 32 0 0 1 32-32zm-448-192a32 32 0 0 1 32-32h192a32 32 0 0 1 0 64H96a32 32 0 0 1-32-32zm640 0a32 32 0 0 1 32-32h192a32 32 0 0 1 0 64H736a32 32 0 0 1-32-32z"
4136
4700
  })
4137
4701
  ])
4138
4702
  ]));
4139
- if (props.tip || slots.tip) {
4140
- loadingChildren.push(createVNode("div", { class: "lyt-spin__tip" }, slots.tip ? slots.tip() : [props.tip]));
4703
+ if (p.tip || slots.tip) {
4704
+ loadingChildren.push(createVNode("div", { class: "lyt-spin__tip" }, slots.tip ? slots.tip() : [createTextVNode(p.tip)]));
4141
4705
  }
4142
4706
  children.push(createVNode("div", { class: "lyt-spin__loading" }, loadingChildren));
4143
4707
  }
@@ -4147,9 +4711,9 @@ var Spin = defineComponent({
4147
4711
  }, slots.default()));
4148
4712
  }
4149
4713
  return createVNode("div", mergeA11yProps({
4150
- id: props.id,
4151
- "aria-label": props.ariaLabel,
4152
- "aria-describedby": props.ariaDescribedBy,
4714
+ id: p.id,
4715
+ "aria-label": p.ariaLabel,
4716
+ "aria-describedby": p.ariaDescribedBy,
4153
4717
  role: "alert",
4154
4718
  "aria-live": "polite"
4155
4719
  }, {
@@ -4172,16 +4736,17 @@ var Empty = defineComponent({
4172
4736
  ariaDescribedBy: { type: String, default: "" }
4173
4737
  },
4174
4738
  setup(props, { slots }) {
4739
+ const p = props;
4175
4740
  const getEmptyClass = () => {
4176
4741
  const classes = ["lyt-empty"];
4177
- if (props.class) classes.push(props.class);
4742
+ if (p.class) classes.push(p.class);
4178
4743
  return classes.join(" ");
4179
4744
  };
4180
4745
  const getEmptyStyle = () => {
4181
- if (!props.style) return void 0;
4182
- if (isString(props.style)) return props.style;
4183
- if (isObject(props.style)) {
4184
- return Object.entries(props.style).map(([key, value]) => `${key}: ${value}`).join("; ");
4746
+ if (!p.style) return void 0;
4747
+ if (isString(p.style)) return p.style;
4748
+ if (isObject(p.style)) {
4749
+ return Object.entries(p.style).map(([key, value]) => `${key}: ${value}`).join("; ");
4185
4750
  }
4186
4751
  return void 0;
4187
4752
  };
@@ -4189,12 +4754,12 @@ var Empty = defineComponent({
4189
4754
  const children = [];
4190
4755
  if (slots.image) {
4191
4756
  children.push(createVNode("div", { class: "lyt-empty__image" }, slots.image()));
4192
- } else if (props.image) {
4757
+ } else if (p.image) {
4193
4758
  children.push(createVNode("div", { class: "lyt-empty__image" }, [
4194
4759
  createVNode("img", {
4195
- src: props.image,
4760
+ src: p.image,
4196
4761
  alt: "empty",
4197
- style: { width: `${props.imageSize}px`, height: `${props.imageSize}px` }
4762
+ style: { width: `${p.imageSize}px`, height: `${p.imageSize}px` }
4198
4763
  })
4199
4764
  ]));
4200
4765
  } else {
@@ -4202,7 +4767,7 @@ var Empty = defineComponent({
4202
4767
  createVNode("svg", {
4203
4768
  viewBox: "0 0 400 320",
4204
4769
  class: "lyt-empty__svg",
4205
- style: { width: `${props.imageSize}px`, height: `${props.imageSize * 0.8}px` }
4770
+ style: { width: `${p.imageSize || 160}px`, height: `${(p.imageSize || 160) * 0.8}px` }
4206
4771
  }, [
4207
4772
  createVNode("g", { fill: "none", "fill-rule": "evenodd" }, [
4208
4773
  createVNode("g", { transform: "translate(40 40)" }, [
@@ -4220,16 +4785,16 @@ var Empty = defineComponent({
4220
4785
  }
4221
4786
  if (slots.description) {
4222
4787
  children.push(createVNode("div", { class: "lyt-empty__description" }, slots.description()));
4223
- } else if (props.description) {
4224
- children.push(createVNode("div", { class: "lyt-empty__description" }, [props.description]));
4788
+ } else if (p.description) {
4789
+ children.push(createVNode("div", { class: "lyt-empty__description" }, [createTextVNode(p.description)]));
4225
4790
  }
4226
4791
  if (slots.default) {
4227
4792
  children.push(createVNode("div", { class: "lyt-empty__bottom" }, slots.default()));
4228
4793
  }
4229
4794
  return createVNode("div", mergeA11yProps({
4230
- id: props.id,
4231
- "aria-label": props.ariaLabel,
4232
- "aria-describedby": props.ariaDescribedBy,
4795
+ id: p.id,
4796
+ "aria-label": p.ariaLabel,
4797
+ "aria-describedby": p.ariaDescribedBy,
4233
4798
  role: "status",
4234
4799
  "aria-live": "polite"
4235
4800
  }, {
@@ -4330,21 +4895,22 @@ var Container = defineComponent({
4330
4895
  style: { type: String, default: "" }
4331
4896
  },
4332
4897
  setup(props, { slots }) {
4898
+ const p = props;
4333
4899
  const getContainerClass = () => {
4334
4900
  const classes = ["lyt-container"];
4335
- if (props.fluid) {
4901
+ if (p.fluid) {
4336
4902
  classes.push("lyt-container--fluid");
4337
4903
  }
4338
- if (props.class) {
4339
- classes.push(props.class);
4904
+ if (p.class) {
4905
+ classes.push(p.class);
4340
4906
  }
4341
4907
  return classes.join(" ");
4342
4908
  };
4343
4909
  const getContainerStyle = () => {
4344
- if (!props.style) return void 0;
4345
- if (isString(props.style)) return props.style;
4346
- if (isObject(props.style)) {
4347
- return Object.entries(props.style).map(([key, value]) => `${key}: ${value}`).join("; ");
4910
+ if (!p.style) return void 0;
4911
+ if (isString(p.style)) return p.style;
4912
+ if (isObject(p.style)) {
4913
+ return Object.entries(p.style).map(([key, value]) => `${key}: ${value}`).join("; ");
4348
4914
  }
4349
4915
  return void 0;
4350
4916
  };
@@ -4372,24 +4938,25 @@ var Divider = defineComponent({
4372
4938
  ariaDescribedBy: { type: String, default: "" }
4373
4939
  },
4374
4940
  setup(props, { slots }) {
4941
+ const p = props;
4375
4942
  const getDividerClass = () => {
4376
4943
  const classes = ["lyt-divider"];
4377
- if (props.type !== "horizontal") {
4378
- classes.push(`lyt-divider--${props.type}`);
4944
+ if (p.type !== "horizontal") {
4945
+ classes.push(`lyt-divider--${p.type}`);
4379
4946
  }
4380
4947
  if (slots.default) {
4381
- classes.push(`lyt-divider--${props.contentPosition}`);
4948
+ classes.push(`lyt-divider--${p.contentPosition}`);
4382
4949
  }
4383
- if (props.class) {
4384
- classes.push(props.class);
4950
+ if (p.class) {
4951
+ classes.push(p.class);
4385
4952
  }
4386
4953
  return classes.join(" ");
4387
4954
  };
4388
4955
  const getDividerStyle = () => {
4389
- if (!props.style) return void 0;
4390
- if (isString(props.style)) return props.style;
4391
- if (isObject(props.style)) {
4392
- return Object.entries(props.style).map(([key, value]) => `${key}: ${value}`).join("; ");
4956
+ if (!p.style) return void 0;
4957
+ if (isString(p.style)) return p.style;
4958
+ if (isObject(p.style)) {
4959
+ return Object.entries(p.style).map(([key, value]) => `${key}: ${value}`).join("; ");
4393
4960
  }
4394
4961
  return void 0;
4395
4962
  };
@@ -4399,9 +4966,9 @@ var Divider = defineComponent({
4399
4966
  children.push(createVNode("span", { class: "lyt-divider__text" }, slots.default()));
4400
4967
  }
4401
4968
  return createVNode("div", mergeA11yProps({
4402
- id: props.id,
4403
- "aria-label": props.ariaLabel,
4404
- "aria-describedby": props.ariaDescribedBy,
4969
+ id: p.id,
4970
+ "aria-label": p.ariaLabel,
4971
+ "aria-describedby": p.ariaDescribedBy,
4405
4972
  role: "separator"
4406
4973
  }, {
4407
4974
  class: getDividerClass(),
@@ -4613,59 +5180,60 @@ var Tooltip = defineComponent({
4613
5180
  ariaDescribedBy: { type: String, default: "" }
4614
5181
  },
4615
5182
  setup(props, { slots }) {
5183
+ const p = props;
4616
5184
  const state = reactive({
4617
- visible: false,
4618
- openTimer: null,
4619
- closeTimer: null
5185
+ visible: false
4620
5186
  });
5187
+ let openTimer = null;
5188
+ let closeTimer = null;
4621
5189
  const handleMouseEnter = () => {
4622
- if (props.disabled || props.trigger !== "hover") return;
4623
- clearTimeout(state.closeTimer);
4624
- if (props.openDelay > 0) {
4625
- state.openTimer = setTimeout(() => {
5190
+ if (p.disabled || p.trigger !== "hover") return;
5191
+ if (closeTimer) clearTimeout(closeTimer);
5192
+ if (p.openDelay && p.openDelay > 0) {
5193
+ openTimer = setTimeout(() => {
4626
5194
  state.visible = true;
4627
- }, props.openDelay);
5195
+ }, p.openDelay);
4628
5196
  } else {
4629
5197
  state.visible = true;
4630
5198
  }
4631
5199
  };
4632
5200
  const handleMouseLeave = () => {
4633
- if (props.disabled || props.trigger !== "hover") return;
4634
- clearTimeout(state.openTimer);
4635
- if (props.closeDelay > 0) {
4636
- state.closeTimer = setTimeout(() => {
5201
+ if (p.disabled || p.trigger !== "hover") return;
5202
+ if (openTimer) clearTimeout(openTimer);
5203
+ if (p.closeDelay && p.closeDelay > 0) {
5204
+ closeTimer = setTimeout(() => {
4637
5205
  state.visible = false;
4638
- }, props.closeDelay);
5206
+ }, p.closeDelay);
4639
5207
  } else {
4640
5208
  state.visible = false;
4641
5209
  }
4642
5210
  };
4643
5211
  const handleClick = () => {
4644
- if (props.disabled || props.trigger !== "click") return;
5212
+ if (p.disabled || p.trigger !== "click") return;
4645
5213
  state.visible = !state.visible;
4646
5214
  };
4647
5215
  const handleFocus = () => {
4648
- if (props.disabled || props.trigger !== "focus") return;
5216
+ if (p.disabled || p.trigger !== "focus") return;
4649
5217
  state.visible = true;
4650
5218
  };
4651
5219
  const handleBlur = () => {
4652
- if (props.disabled || props.trigger !== "focus") return;
5220
+ if (p.disabled || p.trigger !== "focus") return;
4653
5221
  state.visible = false;
4654
5222
  };
4655
5223
  const getTooltipClass = () => {
4656
5224
  const classes = ["lyt-tooltip"];
4657
- classes.push(`lyt-tooltip--${props.placement}`);
4658
- if (props.class) classes.push(props.class);
5225
+ classes.push(`lyt-tooltip--${p.placement}`);
5226
+ if (p.class) classes.push(p.class);
4659
5227
  return classes.join(" ");
4660
5228
  };
4661
5229
  const getTooltipStyle = () => {
4662
5230
  const style = {};
4663
- if (props.style) {
4664
- if (isString(props.style)) {
4665
- return props.style;
5231
+ if (p.style) {
5232
+ if (isString(p.style)) {
5233
+ return p.style;
4666
5234
  }
4667
- if (isObject(props.style)) {
4668
- Object.assign(style, props.style);
5235
+ if (isObject(p.style)) {
5236
+ Object.assign(style, p.style);
4669
5237
  }
4670
5238
  }
4671
5239
  return style;
@@ -4682,17 +5250,17 @@ var Tooltip = defineComponent({
4682
5250
  onBlur: handleBlur
4683
5251
  }, slots.default()));
4684
5252
  }
4685
- if (state.visible && !props.disabled) {
5253
+ if (state.visible && !p.disabled) {
4686
5254
  const contentChildren = [];
4687
- if (props.showArrow) {
5255
+ if (p.showArrow) {
4688
5256
  contentChildren.push(createVNode("div", {
4689
5257
  class: "lyt-tooltip__arrow"
4690
5258
  }, []));
4691
5259
  }
4692
- if (props.content) {
5260
+ if (p.content) {
4693
5261
  contentChildren.push(createVNode("div", {
4694
5262
  class: "lyt-tooltip__content"
4695
- }, [props.content]));
5263
+ }, [createTextVNode(p.content)]));
4696
5264
  } else if (slots.content) {
4697
5265
  contentChildren.push(createVNode("div", {
4698
5266
  class: "lyt-tooltip__content"
@@ -4704,9 +5272,9 @@ var Tooltip = defineComponent({
4704
5272
  }, contentChildren));
4705
5273
  }
4706
5274
  return createVNode("div", mergeA11yProps({
4707
- id: props.id,
4708
- "aria-label": props.ariaLabel,
4709
- "aria-describedby": props.ariaDescribedBy
5275
+ id: p.id,
5276
+ "aria-label": p.ariaLabel,
5277
+ "aria-describedby": p.ariaDescribedBy
4710
5278
  }, {
4711
5279
  class: "lyt-tooltip-wrapper"
4712
5280
  }), children);
@@ -5760,7 +6328,7 @@ var Slider = defineComponent({
5760
6328
  } else {
5761
6329
  state.secondValue = Math.max(newValue, state.firstValue);
5762
6330
  }
5763
- const newModelValue = [state.firstValue, state.secondValue];
6331
+ const newModelValue = _props.range ? [state.firstValue, state.secondValue] : state.firstValue;
5764
6332
  emit("update:modelValue", newModelValue);
5765
6333
  emit("input", newModelValue);
5766
6334
  _props.onInput?.(newModelValue);