@fastkit/vui 0.8.9 → 0.8.12

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/vui.cjs.js CHANGED
@@ -2321,7 +2321,8 @@ const VOption = vue.defineComponent({
2321
2321
  return vue.createVNode("label", vue.mergeProps({
2322
2322
  "class": classes,
2323
2323
  "onClick": selectorItemControl.handleClickElement,
2324
- "tabindex": selectorItemControl.tabindex
2324
+ "tabindex": selectorItemControl.tabindex,
2325
+ "data-value": selectorItemControl.propValue
2325
2326
  }, {
2326
2327
  'aria-disabled': selectorItemControl.isDisabled
2327
2328
  }), [vue.createVNode("span", {
@@ -2482,6 +2483,9 @@ function _isSlot$8(s) {
2482
2483
  return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !vue.isVNode(s);
2483
2484
  }
2484
2485
 
2486
+ const ARROW_KEY_TYPES = vueKit.useKeybord.Key(['ArrowUp', 'ArrowDown']);
2487
+ const CHOICE_KEY_TYPES = vueKit.useKeybord.Key(['Enter', ' ']);
2488
+ const KEYBORD_EVENT_TYPES = vueKit.useKeybord.Key([...ARROW_KEY_TYPES, ...CHOICE_KEY_TYPES]);
2485
2489
  const {
2486
2490
  props: props$3,
2487
2491
  emits: emits$3
@@ -2503,6 +2507,7 @@ const VSelect = vue.defineComponent({
2503
2507
  emits: emits$3,
2504
2508
 
2505
2509
  setup(props, ctx) {
2510
+ const menuRef = vue.ref(null);
2506
2511
  const menuOpened = vue.ref(false);
2507
2512
 
2508
2513
  const showMenu = () => {
@@ -2565,6 +2570,107 @@ const VSelect = vue.defineComponent({
2565
2570
  return children;
2566
2571
  };
2567
2572
 
2573
+ const clearKeyFocused = () => {
2574
+ const menu = menuRef.value;
2575
+ if (!menu) return;
2576
+ const bodyEl = menu.stackMenuControl.bodyRef.value;
2577
+ if (!bodyEl) return;
2578
+ const els = Array.from(bodyEl.querySelectorAll('.v-option'));
2579
+ els.forEach(el => el.classList.remove('v-option--key-focused'));
2580
+ };
2581
+
2582
+ const getItemElements = () => {
2583
+ const menu = menuRef.value;
2584
+ if (!menu) return;
2585
+ const bodyEl = menu.stackMenuControl.bodyRef.value;
2586
+ if (!bodyEl) return;
2587
+ const els = Array.from(bodyEl.querySelectorAll('.v-option')).filter(el => {
2588
+ if (el.tabIndex === -1) return false;
2589
+ const disabled = el.getAttribute('disabled');
2590
+ const ariaDisabled = el.getAttribute('aria-disabled');
2591
+ if (ariaDisabled === 'true') return false;
2592
+ return disabled == null || disabled === '';
2593
+ });
2594
+ if (!els.length) return;
2595
+ return els;
2596
+ };
2597
+
2598
+ const arrowKeyHandler = ev => {
2599
+ const {
2600
+ key
2601
+ } = ev;
2602
+ if (!menuOpened.value || !ARROW_KEY_TYPES.includes(key)) return;
2603
+ const els = getItemElements();
2604
+ if (!els) return;
2605
+ const currentEl = els.find(el => el.classList.contains('v-option--key-focused'));
2606
+ const currentIndex = currentEl && els.indexOf(currentEl);
2607
+ let nextIndex;
2608
+ const {
2609
+ length
2610
+ } = els;
2611
+ const isUp = key === 'ArrowUp';
2612
+
2613
+ if (currentIndex == null) {
2614
+ nextIndex = isUp ? length - 1 : 0;
2615
+ } else {
2616
+ const shiftAmount = key === 'ArrowUp' ? -1 : 1;
2617
+ nextIndex = currentIndex + shiftAmount;
2618
+
2619
+ if (nextIndex < 0) {
2620
+ nextIndex = length - 1;
2621
+ } else if (nextIndex >= length) {
2622
+ nextIndex = 0;
2623
+ }
2624
+ }
2625
+
2626
+ const nextEl = els[nextIndex];
2627
+
2628
+ if (nextEl) {
2629
+ clearKeyFocused();
2630
+ nextEl.classList.add('v-option--key-focused');
2631
+ nextEl.scrollIntoView({
2632
+ block: 'nearest',
2633
+ inline: 'nearest',
2634
+ behavior: 'smooth'
2635
+ });
2636
+ ev.preventDefault();
2637
+ }
2638
+ };
2639
+
2640
+ const choiceKeyHandler = ev => {
2641
+ const {
2642
+ key
2643
+ } = ev;
2644
+ if (!menuOpened.value || !CHOICE_KEY_TYPES.includes(key)) return;
2645
+ const els = getItemElements();
2646
+ if (!els) return;
2647
+ const currentEl = els.find(el => el.classList.contains('v-option--key-focused'));
2648
+
2649
+ if (currentEl) {
2650
+ const ev = new MouseEvent('click', {
2651
+ view: window,
2652
+ bubbles: true,
2653
+ cancelable: true
2654
+ });
2655
+ currentEl.dispatchEvent(ev);
2656
+ ev.preventDefault();
2657
+ vue.nextTick(() => {
2658
+ currentEl.classList.add('v-option--key-focused');
2659
+ });
2660
+ }
2661
+ };
2662
+
2663
+ const keybordEventHandler = ev => {
2664
+ if (ARROW_KEY_TYPES.includes(ev.key)) return arrowKeyHandler(ev);
2665
+ if (CHOICE_KEY_TYPES.includes(ev.key)) return choiceKeyHandler(ev);
2666
+ };
2667
+
2668
+ vueKit.useKeybord([{
2669
+ key: KEYBORD_EVENT_TYPES,
2670
+ handler: keybordEventHandler
2671
+ }], {
2672
+ autorun: true
2673
+ });
2568
2674
  return { ...inputControl.expose(),
2569
2675
  ...control,
2570
2676
  iconName,
@@ -2573,7 +2679,9 @@ const VSelect = vue.defineComponent({
2573
2679
  focus,
2574
2680
  showMenu,
2575
2681
  closeMenu,
2576
- renderSelections
2682
+ renderSelections,
2683
+ menuRef: () => menuRef,
2684
+ clearKeyFocused
2577
2685
  };
2578
2686
  },
2579
2687
 
@@ -2624,15 +2732,8 @@ const VSelect = vue.defineComponent({
2624
2732
  "alwaysRender": true,
2625
2733
  "modelValue": this.menuOpened,
2626
2734
  "onUpdate:modelValue": $event => this.menuOpened = $event,
2627
- "itemElements": body => body.querySelectorAll('.v-option'),
2628
- "onChoiceItemElement": item => {
2629
- const ev = new MouseEvent('click', {
2630
- view: window,
2631
- bubbles: true,
2632
- cancelable: true
2633
- });
2634
- item.dispatchEvent(ev);
2635
- }
2735
+ "ref": this.menuRef(),
2736
+ "onClose": this.clearKeyFocused
2636
2737
  }, {
2637
2738
  default: () => [vue.createVNode("div", {
2638
2739
  "class": ['v-select__body', this.classes]
@@ -5593,15 +5694,7 @@ function mergeVuiPluginOptions(base, override) {
5593
5694
  return merged;
5594
5695
  }
5595
5696
  class VuiPlugin {
5596
- static installedApps = new Set();
5597
-
5598
5697
  static install(app, opts) {
5599
- const {
5600
- installedApps
5601
- } = this;
5602
- if (installedApps.has(app)) return;
5603
- const unmountApp = app.unmount;
5604
- installedApps.add(app);
5605
5698
  const {
5606
5699
  colorScheme,
5607
5700
  stack,
@@ -5636,12 +5729,9 @@ class VuiPlugin {
5636
5729
  const $vui = new VuiService(opts, app.config.globalProperties.$vstack);
5637
5730
  app.provide(VuiInjectionKey, $vui);
5638
5731
  app.config.globalProperties.$vui = $vui;
5639
-
5640
- app.unmount = function () {
5641
- installedApps.delete(app);
5732
+ vueKit.onAppUnmount(app, () => {
5642
5733
  delete app.config.globalProperties.$vui;
5643
- unmountApp();
5644
- };
5734
+ });
5645
5735
  }
5646
5736
 
5647
5737
  }
@@ -5654,11 +5744,14 @@ exports.VMenu = vueKit.VMenu;
5654
5744
  exports.VSnackbar = vueKit.VSnackbar;
5655
5745
  exports.VTooltip = vueKit.VTooltip;
5656
5746
  exports.ICON_NAMES = iconFont.ICON_NAMES;
5747
+ exports.ARROW_KEY_TYPES = ARROW_KEY_TYPES;
5657
5748
  exports.AVATAR_SIZES = AVATAR_SIZES;
5658
5749
  exports.CHIP_SIZES = CHIP_SIZES;
5750
+ exports.CHOICE_KEY_TYPES = CHOICE_KEY_TYPES;
5659
5751
  exports.CONTROL_FIELD_VARIANTS = CONTROL_FIELD_VARIANTS;
5660
5752
  exports.CONTROL_LOADING_SPINNER_SIZES = CONTROL_LOADING_SPINNER_SIZES;
5661
5753
  exports.CONTROL_SIZES = CONTROL_SIZES;
5754
+ exports.KEYBORD_EVENT_TYPES = KEYBORD_EVENT_TYPES;
5662
5755
  exports.PAGINATION_ALIGNS = PAGINATION_ALIGNS;
5663
5756
  exports.VApp = VApp;
5664
5757
  exports.VAvatar = VAvatar;
@@ -2321,7 +2321,8 @@ const VOption = vue.defineComponent({
2321
2321
  return vue.createVNode("label", vue.mergeProps({
2322
2322
  "class": classes,
2323
2323
  "onClick": selectorItemControl.handleClickElement,
2324
- "tabindex": selectorItemControl.tabindex
2324
+ "tabindex": selectorItemControl.tabindex,
2325
+ "data-value": selectorItemControl.propValue
2325
2326
  }, {
2326
2327
  'aria-disabled': selectorItemControl.isDisabled
2327
2328
  }), [vue.createVNode("span", {
@@ -2482,6 +2483,9 @@ function _isSlot$8(s) {
2482
2483
  return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !vue.isVNode(s);
2483
2484
  }
2484
2485
 
2486
+ const ARROW_KEY_TYPES = vueKit.useKeybord.Key(['ArrowUp', 'ArrowDown']);
2487
+ const CHOICE_KEY_TYPES = vueKit.useKeybord.Key(['Enter', ' ']);
2488
+ const KEYBORD_EVENT_TYPES = vueKit.useKeybord.Key([...ARROW_KEY_TYPES, ...CHOICE_KEY_TYPES]);
2485
2489
  const {
2486
2490
  props: props$3,
2487
2491
  emits: emits$3
@@ -2503,6 +2507,7 @@ const VSelect = vue.defineComponent({
2503
2507
  emits: emits$3,
2504
2508
 
2505
2509
  setup(props, ctx) {
2510
+ const menuRef = vue.ref(null);
2506
2511
  const menuOpened = vue.ref(false);
2507
2512
 
2508
2513
  const showMenu = () => {
@@ -2565,6 +2570,107 @@ const VSelect = vue.defineComponent({
2565
2570
  return children;
2566
2571
  };
2567
2572
 
2573
+ const clearKeyFocused = () => {
2574
+ const menu = menuRef.value;
2575
+ if (!menu) return;
2576
+ const bodyEl = menu.stackMenuControl.bodyRef.value;
2577
+ if (!bodyEl) return;
2578
+ const els = Array.from(bodyEl.querySelectorAll('.v-option'));
2579
+ els.forEach(el => el.classList.remove('v-option--key-focused'));
2580
+ };
2581
+
2582
+ const getItemElements = () => {
2583
+ const menu = menuRef.value;
2584
+ if (!menu) return;
2585
+ const bodyEl = menu.stackMenuControl.bodyRef.value;
2586
+ if (!bodyEl) return;
2587
+ const els = Array.from(bodyEl.querySelectorAll('.v-option')).filter(el => {
2588
+ if (el.tabIndex === -1) return false;
2589
+ const disabled = el.getAttribute('disabled');
2590
+ const ariaDisabled = el.getAttribute('aria-disabled');
2591
+ if (ariaDisabled === 'true') return false;
2592
+ return disabled == null || disabled === '';
2593
+ });
2594
+ if (!els.length) return;
2595
+ return els;
2596
+ };
2597
+
2598
+ const arrowKeyHandler = ev => {
2599
+ const {
2600
+ key
2601
+ } = ev;
2602
+ if (!menuOpened.value || !ARROW_KEY_TYPES.includes(key)) return;
2603
+ const els = getItemElements();
2604
+ if (!els) return;
2605
+ const currentEl = els.find(el => el.classList.contains('v-option--key-focused'));
2606
+ const currentIndex = currentEl && els.indexOf(currentEl);
2607
+ let nextIndex;
2608
+ const {
2609
+ length
2610
+ } = els;
2611
+ const isUp = key === 'ArrowUp';
2612
+
2613
+ if (currentIndex == null) {
2614
+ nextIndex = isUp ? length - 1 : 0;
2615
+ } else {
2616
+ const shiftAmount = key === 'ArrowUp' ? -1 : 1;
2617
+ nextIndex = currentIndex + shiftAmount;
2618
+
2619
+ if (nextIndex < 0) {
2620
+ nextIndex = length - 1;
2621
+ } else if (nextIndex >= length) {
2622
+ nextIndex = 0;
2623
+ }
2624
+ }
2625
+
2626
+ const nextEl = els[nextIndex];
2627
+
2628
+ if (nextEl) {
2629
+ clearKeyFocused();
2630
+ nextEl.classList.add('v-option--key-focused');
2631
+ nextEl.scrollIntoView({
2632
+ block: 'nearest',
2633
+ inline: 'nearest',
2634
+ behavior: 'smooth'
2635
+ });
2636
+ ev.preventDefault();
2637
+ }
2638
+ };
2639
+
2640
+ const choiceKeyHandler = ev => {
2641
+ const {
2642
+ key
2643
+ } = ev;
2644
+ if (!menuOpened.value || !CHOICE_KEY_TYPES.includes(key)) return;
2645
+ const els = getItemElements();
2646
+ if (!els) return;
2647
+ const currentEl = els.find(el => el.classList.contains('v-option--key-focused'));
2648
+
2649
+ if (currentEl) {
2650
+ const ev = new MouseEvent('click', {
2651
+ view: window,
2652
+ bubbles: true,
2653
+ cancelable: true
2654
+ });
2655
+ currentEl.dispatchEvent(ev);
2656
+ ev.preventDefault();
2657
+ vue.nextTick(() => {
2658
+ currentEl.classList.add('v-option--key-focused');
2659
+ });
2660
+ }
2661
+ };
2662
+
2663
+ const keybordEventHandler = ev => {
2664
+ if (ARROW_KEY_TYPES.includes(ev.key)) return arrowKeyHandler(ev);
2665
+ if (CHOICE_KEY_TYPES.includes(ev.key)) return choiceKeyHandler(ev);
2666
+ };
2667
+
2668
+ vueKit.useKeybord([{
2669
+ key: KEYBORD_EVENT_TYPES,
2670
+ handler: keybordEventHandler
2671
+ }], {
2672
+ autorun: true
2673
+ });
2568
2674
  return { ...inputControl.expose(),
2569
2675
  ...control,
2570
2676
  iconName,
@@ -2573,7 +2679,9 @@ const VSelect = vue.defineComponent({
2573
2679
  focus,
2574
2680
  showMenu,
2575
2681
  closeMenu,
2576
- renderSelections
2682
+ renderSelections,
2683
+ menuRef: () => menuRef,
2684
+ clearKeyFocused
2577
2685
  };
2578
2686
  },
2579
2687
 
@@ -2624,15 +2732,8 @@ const VSelect = vue.defineComponent({
2624
2732
  "alwaysRender": true,
2625
2733
  "modelValue": this.menuOpened,
2626
2734
  "onUpdate:modelValue": $event => this.menuOpened = $event,
2627
- "itemElements": body => body.querySelectorAll('.v-option'),
2628
- "onChoiceItemElement": item => {
2629
- const ev = new MouseEvent('click', {
2630
- view: window,
2631
- bubbles: true,
2632
- cancelable: true
2633
- });
2634
- item.dispatchEvent(ev);
2635
- }
2735
+ "ref": this.menuRef(),
2736
+ "onClose": this.clearKeyFocused
2636
2737
  }, {
2637
2738
  default: () => [vue.createVNode("div", {
2638
2739
  "class": ['v-select__body', this.classes]
@@ -5593,15 +5694,7 @@ function mergeVuiPluginOptions(base, override) {
5593
5694
  return merged;
5594
5695
  }
5595
5696
  class VuiPlugin {
5596
- static installedApps = new Set();
5597
-
5598
5697
  static install(app, opts) {
5599
- const {
5600
- installedApps
5601
- } = this;
5602
- if (installedApps.has(app)) return;
5603
- const unmountApp = app.unmount;
5604
- installedApps.add(app);
5605
5698
  const {
5606
5699
  colorScheme,
5607
5700
  stack,
@@ -5636,12 +5729,9 @@ class VuiPlugin {
5636
5729
  const $vui = new VuiService(opts, app.config.globalProperties.$vstack);
5637
5730
  app.provide(VuiInjectionKey, $vui);
5638
5731
  app.config.globalProperties.$vui = $vui;
5639
-
5640
- app.unmount = function () {
5641
- installedApps.delete(app);
5732
+ vueKit.onAppUnmount(app, () => {
5642
5733
  delete app.config.globalProperties.$vui;
5643
- unmountApp();
5644
- };
5734
+ });
5645
5735
  }
5646
5736
 
5647
5737
  }
@@ -5654,11 +5744,14 @@ exports.VMenu = vueKit.VMenu;
5654
5744
  exports.VSnackbar = vueKit.VSnackbar;
5655
5745
  exports.VTooltip = vueKit.VTooltip;
5656
5746
  exports.ICON_NAMES = iconFont.ICON_NAMES;
5747
+ exports.ARROW_KEY_TYPES = ARROW_KEY_TYPES;
5657
5748
  exports.AVATAR_SIZES = AVATAR_SIZES;
5658
5749
  exports.CHIP_SIZES = CHIP_SIZES;
5750
+ exports.CHOICE_KEY_TYPES = CHOICE_KEY_TYPES;
5659
5751
  exports.CONTROL_FIELD_VARIANTS = CONTROL_FIELD_VARIANTS;
5660
5752
  exports.CONTROL_LOADING_SPINNER_SIZES = CONTROL_LOADING_SPINNER_SIZES;
5661
5753
  exports.CONTROL_SIZES = CONTROL_SIZES;
5754
+ exports.KEYBORD_EVENT_TYPES = KEYBORD_EVENT_TYPES;
5662
5755
  exports.PAGINATION_ALIGNS = PAGINATION_ALIGNS;
5663
5756
  exports.VApp = VApp;
5664
5757
  exports.VAvatar = VAvatar;
package/dist/vui.css CHANGED
@@ -1880,7 +1880,7 @@ thead th {
1880
1880
  .v-option::after {
1881
1881
  background: var(--palette-primary);
1882
1882
  }
1883
- .v-option:hover::before, .v-option:focus-within::before {
1883
+ .v-option:hover::before, .v-option:focus-within::before, .v-option--key-focused::before {
1884
1884
  opacity: 0.12;
1885
1885
  }
1886
1886
  .v-option__label {