All files / src/components/modal ActionRail.vue

82.75% Statements 24/29
25% Branches 1/4
88.88% Functions 8/9
82.14% Lines 23/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113            6x                         10x         6x                                                       10x             10x 10x     10x               10x 18x     10x 18x     10x 18x     10x 10x 10x 10x     10x 1x         63x 10x 10x 63x                            
<template>
  <div
    ref="rail"
    class="action-rail d-flex"
  >
    <template v-for="(action, index) of visible">
      <div class="visible-item">
        <slot
          name="visible"
          :action="action"
          :index="index"
        ></slot>
      </div>
    </template>
    <v-menu
      v-if="showMenu"
      v-bind="menuProps"
      v-on="menuOn"
    >
      <template #activator="{ props }">
        <v-btn v-bind="mergeProps(props, menuActivatorProps)" />
      </template>
      <v-list>
        <template v-for="(action, index) of hidden">
          <div class="hidden-item">
            <slot
              name="hidden"
              :action="action"
              :index="index"
            ></slot>
          </div>
        </template>
      </v-list>
    </v-menu>
  </div>
</template>
 
<script setup lang="ts">
import { computed, mergeProps, onBeforeUnmount, onMounted, ref } from 'vue';
 
type Action<T = Record<string, unknown>> = {
  width: number;
  props: Record<string, any>;
} & T;
 
interface Props {
  actions?: Action[];
  menuProps?: Record<string, unknown>;
  menuOn?: Record<string, unknown>;
  menuActivatorProps?: Record<string, unknown>;
}
 
const props = withDefaults(defineProps<Props>(), {
  actions: () => [],
  menuProps: () => ({}),
  menuOn: () => ({}),
  menuActivatorProps: () => ({}),
});
 
const rail = ref<HTMLDivElement>();
const splitIndex = ref<number>(0);
 
/* v8 ignore start -- @preserve */
const observer = new ResizeObserver((entries) => {
  if (entries.length > 0) {
    const railWidth = entries[0].contentRect.width;
    calculateSplitIndex(railWidth);
  }
});
/* v8 ignore stop -- @preserve */
 
const visible = computed(() => {
  return props.actions.slice(0, splitIndex.value);
});
 
const hidden = computed(() => {
  return props.actions.slice(splitIndex.value);
});
 
const showMenu = computed(() => {
  return hidden.value.length > 0;
});
 
onMounted(() => {
  observer.observe(rail.value!);
  const rect = rail.value!.getBoundingClientRect();
  calculateSplitIndex(rect.width);
});
 
onBeforeUnmount(() => {
  observer.disconnect();
});
 
/* v8 ignore start -- @preserve */
function calculateSplitIndex(railWidth: number): void {
  const widths = props.actions.map((action) => action.width);
  let sum = 48;
  for (const [idx, width] of widths.entries()) {
    Iif (sum + width < railWidth) {
      sum += width;
      splitIndex.value = idx + 1;
    }
  }
}
/* v8 ignore stop -- @preserve */
</script>
 
<style lang="scss">
.action-rail {
  min-width: 0;
}
</style>