vunor 0.0.2

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.
Files changed (60) hide show
  1. package/README.md +33 -0
  2. package/dist/theme.d.ts +133 -0
  3. package/dist/theme.mjs +1149 -0
  4. package/dist/vite.d.ts +5 -0
  5. package/dist/vite.mjs +13 -0
  6. package/package.json +83 -0
  7. package/src/components/AppLayout/AppLayout.vue +114 -0
  8. package/src/components/Button/Button.vue +49 -0
  9. package/src/components/Button/ButtonBase.vue +43 -0
  10. package/src/components/Button/index.ts +1 -0
  11. package/src/components/Button/shortcuts.ts +29 -0
  12. package/src/components/Card/Card.vue +47 -0
  13. package/src/components/Card/CardHeader.vue +26 -0
  14. package/src/components/Card/CardInner.vue +5 -0
  15. package/src/components/Card/index.ts +3 -0
  16. package/src/components/Card/pi.ts +46 -0
  17. package/src/components/Card/shortcuts.ts +19 -0
  18. package/src/components/Checkbox/Checkbox.vue +59 -0
  19. package/src/components/Checkbox/index.ts +1 -0
  20. package/src/components/Checkbox/shortcuts.ts +27 -0
  21. package/src/components/Combobox/Combobox.vue +502 -0
  22. package/src/components/Combobox/index.ts +2 -0
  23. package/src/components/Combobox/shortcuts.ts +12 -0
  24. package/src/components/Combobox/types.ts +33 -0
  25. package/src/components/Icon/Icon.vue +9 -0
  26. package/src/components/Icon/index.ts +1 -0
  27. package/src/components/Input/Input.vue +109 -0
  28. package/src/components/Input/InputShell.vue +133 -0
  29. package/src/components/Input/index.ts +4 -0
  30. package/src/components/Input/pi.ts +18 -0
  31. package/src/components/Input/types.ts +52 -0
  32. package/src/components/Input/utils.ts +108 -0
  33. package/src/components/Label/Label.vue +6 -0
  34. package/src/components/Label/index.ts +1 -0
  35. package/src/components/Loading/LoadingIndicator.vue +23 -0
  36. package/src/components/Loading/index.ts +1 -0
  37. package/src/components/Loading/shortcuts.ts +9 -0
  38. package/src/components/Menu/Menu.vue +100 -0
  39. package/src/components/Menu/MenuItem.vue +20 -0
  40. package/src/components/Menu/index.ts +2 -0
  41. package/src/components/Menu/shortcuts.ts +6 -0
  42. package/src/components/OverflowContainer/OverflowContainer.vue +120 -0
  43. package/src/components/OverflowContainer/index.ts +1 -0
  44. package/src/components/Pagination/Pagination.vue +71 -0
  45. package/src/components/Popover/Popover.vue +58 -0
  46. package/src/components/Popover/index.ts +1 -0
  47. package/src/components/RadioGroup/RadioGroup.vue +83 -0
  48. package/src/components/RadioGroup/index.ts +1 -0
  49. package/src/components/RadioGroup/shortcuts.ts +34 -0
  50. package/src/components/Select/Select.vue +93 -0
  51. package/src/components/Select/SelectBase.vue +148 -0
  52. package/src/components/Select/index.ts +3 -0
  53. package/src/components/Select/shortcuts.ts +30 -0
  54. package/src/components/Select/types.ts +30 -0
  55. package/src/components/Slider/Slider.vue +73 -0
  56. package/src/components/Slider/index.ts +1 -0
  57. package/src/components/Slider/shortcuts.ts +23 -0
  58. package/src/components/shortcuts.ts +21 -0
  59. package/src/components/utils/index.ts +1 -0
  60. package/src/components/utils/provide-inject.ts +39 -0
@@ -0,0 +1,133 @@
1
+ <script setup lang="ts">
2
+ import { useInputPi } from './pi'
3
+ import type { TInputShellProps, TInputShellEmits } from './types'
4
+ import { useInputDataAttrs, useHtmlInputAttrs } from './utils'
5
+
6
+ const props = withDefaults(defineProps<TInputShellProps>(), {
7
+ design: 'flat',
8
+ rows: 3,
9
+ })
10
+
11
+ const emit = defineEmits<TInputShellEmits>()
12
+
13
+ const modelValue = defineModel<string | number>()
14
+
15
+ const focused = ref<boolean>(false)
16
+
17
+ useInputPi().inject(focused)
18
+
19
+ const attrs = useInputDataAttrs()
20
+ const inputAttrs = useHtmlInputAttrs()
21
+
22
+ function onFocus(event: FocusEvent) {
23
+ focused.value = true
24
+ emit('focus', event)
25
+ }
26
+
27
+ function onBlur(event: FocusEvent) {
28
+ focused.value = false
29
+ emit('blur', event)
30
+ }
31
+
32
+ function taGrow(event: Event) {
33
+ if (props.autoGrow) {
34
+ const ta = event.target as HTMLTextAreaElement | undefined
35
+ if (ta) {
36
+ ta.style.height = 'auto'
37
+ ta.style.height = ta.scrollHeight + 'px'
38
+ }
39
+ }
40
+ }
41
+
42
+ function focusInput(event: MouseEvent) {
43
+ const input = (event.target as HTMLDivElement | undefined)?.querySelector(
44
+ 'input, textarea'
45
+ ) as HTMLInputElement
46
+ if (input) {
47
+ input.focus()
48
+ }
49
+ }
50
+ </script>
51
+
52
+ <template>
53
+ <Primitive
54
+ @click="focusInput"
55
+ class="i8 group/i8 flex-grow"
56
+ :class="{
57
+ 'i8-flat': design === 'flat',
58
+ 'i8-filled': design === 'filled' || design === 'round',
59
+ 'i8-round': design === 'round',
60
+ 'segmented': groupItem,
61
+ }"
62
+ v-bind="attrs"
63
+ :data-active="focused || active"
64
+ >
65
+ <span class="i8-underline" />
66
+ <span class="absolute left-0 right-0 top-0 bottom-0" v-if="!!$slots.overlay">
67
+ <slot name="overlay"></slot>
68
+ </span>
69
+
70
+ <div
71
+ v-if="$slots.prepend || !!iconPrepend"
72
+ class="i8-prepend"
73
+ :class="{
74
+ 'i8-icon-clickable': !!onPrependClick,
75
+ }"
76
+ >
77
+ <slot name="prepend" v-bind="attrs">
78
+ <VuIcon :name="iconPrepend!" @click="emit('prependClick', $event)" />
79
+ </slot>
80
+ </div>
81
+
82
+ <div class="i8-input-wrapper">
83
+ <div
84
+ v-if="!!label"
85
+ class="i8-label-wrapper"
86
+ :data-has-prepend="inputAttrs?.['data-has-prepend']"
87
+ :data-has-append="inputAttrs?.['data-has-append']"
88
+ >
89
+ <label v-if="!!label" class="i8-label" :data-required="required">{{ label }}</label>
90
+ </div>
91
+
92
+ <slot v-bind="inputAttrs!" :onFocus :onBlur>
93
+ <div v-if="type === 'textarea'" class="i8-ta-wrapper">
94
+ <textarea
95
+ v-bind="inputAttrs"
96
+ style="resize: none"
97
+ class="i8-textarea"
98
+ v-model="modelValue"
99
+ @input="taGrow"
100
+ @focus="onFocus"
101
+ @blur="onBlur"
102
+ />
103
+ </div>
104
+ <input
105
+ v-else
106
+ v-bind="inputAttrs"
107
+ class="i8-input"
108
+ v-model="modelValue"
109
+ @focus="onFocus"
110
+ @blur="onBlur"
111
+ />
112
+ </slot>
113
+ </div>
114
+
115
+ <div
116
+ v-if="$slots.append || !!iconAppend || loading"
117
+ class="i8-append"
118
+ :class="{
119
+ 'i8-icon-clickable': !!onAppendClick,
120
+ }"
121
+ >
122
+ <VuLoadingIndicator v-if="loading" class="text-grey" />
123
+ <slot
124
+ name="append"
125
+ v-bind="attrs"
126
+ :emitClick="(event: MouseEvent) => emit('appendClick', event)"
127
+ :iconAppend
128
+ >
129
+ <VuIcon :name="iconAppend!" @click="emit('appendClick', $event)" />
130
+ </slot>
131
+ </div>
132
+ </Primitive>
133
+ </template>
@@ -0,0 +1,4 @@
1
+ export { default as VuInput } from './Input.vue'
2
+ export { default as VuInputShell } from './InputShell.vue'
3
+ export * from './types'
4
+ export * from './utils'
@@ -0,0 +1,18 @@
1
+ import { computed, onUnmounted, ref } from 'vue'
2
+
3
+ import { useProvideInject } from '../utils'
4
+
5
+ export const useInputPi = () =>
6
+ useProvideInject('__vunor_input_PI', () => {
7
+ const focused = computed(() => !!inputs.value.some(a => a.value))
8
+ const inputs = ref<Array<Ref<boolean>>>([])
9
+ return {
10
+ _provide: () => ({ focused }),
11
+ _inject: (a: Ref<boolean>) => {
12
+ inputs.value.push(a)
13
+ onUnmounted(() => {
14
+ inputs.value = inputs.value.filter(i => i !== a)
15
+ })
16
+ },
17
+ }
18
+ })
@@ -0,0 +1,52 @@
1
+ export interface TInputAttrs {
2
+ 'placeholder'?: string
3
+ 'type'?: string
4
+ 'rows'?: number
5
+ 'required'?: boolean
6
+ 'maxlength'?: number
7
+ 'data-has-prepend': boolean
8
+ 'data-has-append': boolean
9
+ 'data-has-label': boolean
10
+ 'disabled'?: boolean
11
+ 'readonly'?: boolean
12
+ 'autocomplete'?: string
13
+ }
14
+
15
+ export interface TInputShellProps
16
+ extends Omit<TInputAttrs, 'data-has-prepend' | 'data-has-append' | 'data-has-label'> {
17
+ label?: string
18
+ design?: 'flat' | 'filled' | 'round'
19
+ iconPrepend?: string
20
+ iconAppend?: string
21
+ groupItem?: boolean
22
+ autoGrow?: boolean
23
+ active?: boolean
24
+ loading?: boolean
25
+ onAppendClick?: (event: MouseEvent) => void
26
+ onPrependClick?: (event: MouseEvent) => void
27
+ onBlur?: (event: FocusEvent) => void
28
+ onFocus?: (event: FocusEvent) => void
29
+ }
30
+
31
+ export interface TInputProps extends TInputShellProps {
32
+ iconBefore?: string
33
+ iconAfter?: string
34
+ error?: string | boolean
35
+ hint?: string
36
+ groupTemplate?: string
37
+ stackLabel?: boolean
38
+ onBeforeClick?: (event: MouseEvent) => void
39
+ onAfterClick?: (event: MouseEvent) => void
40
+ onClick?: (event: MouseEvent) => void
41
+ onBlur?: (event: FocusEvent) => void
42
+ onFocus?: (event: FocusEvent) => void
43
+ }
44
+
45
+ export interface TInputShellEmits {
46
+ (e: 'prependClick' | 'appendClick', event: MouseEvent): void
47
+ (e: 'blur' | 'focus', event: FocusEvent): void
48
+ }
49
+
50
+ export interface TInputEmits extends TInputShellEmits {
51
+ (e: 'beforeClick' | 'afterClick' | 'click', event: MouseEvent): void
52
+ }
@@ -0,0 +1,108 @@
1
+ /* eslint-disable @typescript-eslint/strict-boolean-expressions */
2
+ import type { ComputedRef } from 'vue'
3
+ import { computed, getCurrentInstance } from 'vue'
4
+
5
+ import type { TInputAttrs, TInputProps, TInputShellProps } from './types'
6
+
7
+ export function useHtmlInputAttrs(): ComputedRef<TInputAttrs> | undefined {
8
+ const instance = getCurrentInstance()
9
+ if (instance) {
10
+ const props = instance.props as unknown as TInputShellProps
11
+ return computed(() => ({
12
+ 'placeholder': props.placeholder,
13
+ 'type': props.type ?? 'text',
14
+ 'rows': props.rows,
15
+ 'required': props.required,
16
+ 'disabled': props.disabled,
17
+ 'readonly': props.readonly,
18
+ 'autocomplete': props.autocomplete,
19
+ 'data-has-prepend': !!instance.slots.prepend || !!instance.props.iconPrepend,
20
+ 'data-has-append':
21
+ !!instance.slots.append || !!instance.props.iconAppend || !!instance.props.loading,
22
+ 'data-has-label': !!props.label,
23
+ }))
24
+ }
25
+ }
26
+
27
+ export function useInputProps(): ComputedRef<TInputProps> | undefined {
28
+ const instance = getCurrentInstance()
29
+ if (instance) {
30
+ const props = instance.props as unknown as TInputProps
31
+ return computed(() => ({
32
+ label: props.label,
33
+ stackLabel: props.stackLabel,
34
+ placeholder: props.placeholder,
35
+ design: props.design,
36
+ readonly: props.readonly,
37
+ disabled: props.disabled,
38
+ iconPrepend: props.iconPrepend,
39
+ iconAppend: props.iconAppend,
40
+ groupItem: props.groupItem,
41
+ type: props.type,
42
+ rows: props.type === 'textarea' ? props.rows : undefined,
43
+ autoGrow: props.autoGrow,
44
+ maxlength: props.maxlength,
45
+ required: props.required,
46
+ active: props.active,
47
+ loading: props.loading,
48
+ onAppendClick: props.onAppendClick,
49
+ onPrependClick: props.onPrependClick,
50
+ onBlur: props.onBlur,
51
+ onFocus: props.onFocus,
52
+ iconBefore: props.iconBefore,
53
+ iconAfter: props.iconAfter,
54
+ error: props.error,
55
+ hint: props.hint,
56
+ autocomplete: props.autocomplete,
57
+ onBeforeClick: props.onBeforeClick,
58
+ onAfterClick: props.onAfterClick,
59
+ onClick: props.onClick,
60
+ }))
61
+ }
62
+ }
63
+ export function useInputShellProps(): ComputedRef<TInputShellProps> | undefined {
64
+ const instance = getCurrentInstance()
65
+ if (instance) {
66
+ const props = instance.props as unknown as TInputProps
67
+ return computed(() => ({
68
+ label: props.stackLabel ? undefined : props.label,
69
+ placeholder: props.placeholder,
70
+ readonly: props.readonly,
71
+ design: props.design,
72
+ disabled: props.disabled,
73
+ loading: props.loading,
74
+ iconPrepend: props.iconPrepend,
75
+ iconAppend: props.iconAppend,
76
+ groupItem: props.groupItem,
77
+ type: props.type,
78
+ rows: props.type === 'textarea' ? props.rows : undefined,
79
+ autoGrow: props.autoGrow,
80
+ maxlength: props.maxlength,
81
+ required: props.required,
82
+ active: props.active,
83
+ autocomplete: props.autocomplete,
84
+ onAppendClick: props.onAppendClick,
85
+ onPrependClick: props.onPrependClick,
86
+ onBlur: props.onBlur,
87
+ onFocus: props.onFocus,
88
+ }))
89
+ }
90
+ return undefined
91
+ }
92
+
93
+ export function useInputDataAttrs() {
94
+ const instance = getCurrentInstance() as unknown as
95
+ | {
96
+ props: TInputProps
97
+ setupState?: { modelValue: string | number | undefined }
98
+ }
99
+ | undefined
100
+ return computed(() => ({
101
+ 'data-has-label': instance?.props.label ? '' : undefined,
102
+ 'data-has-placeholder': instance?.props.placeholder ? '' : undefined,
103
+ 'data-has-value':
104
+ instance?.setupState?.modelValue || instance?.setupState?.modelValue === 0 ? '' : undefined,
105
+ 'data-type': instance?.props.type ?? 'text',
106
+ 'aria-disabled': instance?.props.disabled ? true : undefined,
107
+ }))
108
+ }
@@ -0,0 +1,6 @@
1
+ <script setup></script>
2
+ <template>
3
+ <label class="max-w-100% overflow-hidden text-ellipsis whitespace-nowrap text-body fw-$bold">
4
+ <slot></slot>
5
+ </label>
6
+ </template>
@@ -0,0 +1 @@
1
+ export { default as VuLabel } from './Label.vue'
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ withDefaults(defineProps<{ size: string }>(), { size: '1em' })
3
+ </script>
4
+
5
+ <template>
6
+ <svg
7
+ :width="size"
8
+ :height="size"
9
+ viewBox="0 0 14 14"
10
+ fill="none"
11
+ class="loading-indicator loading-indicator-ring"
12
+ >
13
+ <circle
14
+ cx="50%"
15
+ cy="50%"
16
+ r="6"
17
+ stroke="currentColor"
18
+ stroke-dasharray="38 38"
19
+ stroke-width="1"
20
+ class=""
21
+ />
22
+ </svg>
23
+ </template>
@@ -0,0 +1 @@
1
+ export { default as VuLoadingIndicator } from './LoadingIndicator.vue'
@@ -0,0 +1,9 @@
1
+ import { scFromObject } from '../../theme/utils/shortcut-obj'
2
+
3
+ export const loadingShortcuts = {
4
+ 'loading-indicator': 'cursor-wait',
5
+ 'loading-indicator-ring': scFromObject({
6
+ '': 'animate-spin animate-duration-1500',
7
+ '[&>circle]:': 'animate-loading-dashoffset animate-count-infinite animate-duration-2500',
8
+ }),
9
+ }
@@ -0,0 +1,100 @@
1
+ <script setup lang="ts">
2
+ import VuMenuItem from './MenuItem.vue'
3
+ import VuInput from '../Input/Input.vue'
4
+
5
+ type AcceptableValue = string | number | boolean | Record<string, any>
6
+ type TItem = { label: string; value: AcceptableValue; icon?: string; group?: string }
7
+ type Props = {
8
+ items: (string | TItem)[]
9
+ emptyText?: string
10
+ }
11
+ const props = defineProps<Props>()
12
+
13
+ const modelValue = defineModel<AcceptableValue>()
14
+
15
+ const groups = computed(() => {
16
+ const grps = {} as Record<string, TItem[]>
17
+ for (const item of props.items) {
18
+ const _item: TItem =
19
+ typeof item === 'string'
20
+ ? {
21
+ label: item,
22
+ value: item,
23
+ icon: '',
24
+ group: '',
25
+ }
26
+ : item
27
+ if (!_item.group) {
28
+ _item.group = ''
29
+ }
30
+ grps[_item.group] = grps[_item.group] || []
31
+ grps[_item.group].push(_item)
32
+ }
33
+ return Object.entries(grps)
34
+ .sort(([a], [b]) => (a === '' ? -1 : b === '' ? 1 : 0))
35
+ .map(([group, items]) => ({ group, items }))
36
+ })
37
+
38
+ function handleHomeEnd(event: KeyboardEvent) {
39
+ const target = event.target as HTMLInputElement
40
+ const length = event.key === 'Home' ? 0 : target.value.length
41
+ if (event.shiftKey) {
42
+ target.setSelectionRange(
43
+ event.key === 'Home' ? 0 : target.selectionEnd ?? target.value.length,
44
+ event.key === 'Home' ? target.selectionStart ?? 0 : target.value.length
45
+ )
46
+ } else {
47
+ target.setSelectionRange(length, length)
48
+ }
49
+ }
50
+ </script>
51
+
52
+ <template>
53
+ <ComboboxRoot
54
+ :open="true"
55
+ model-value=""
56
+ @update:model-value="modelValue = $event"
57
+ class="menu-root"
58
+ >
59
+ <!-- input -->
60
+ <ComboboxInput
61
+ auto-focus
62
+ design="flat"
63
+ icon-prepend="i--search"
64
+ class="px-$m mb-$s"
65
+ placeholder="Search"
66
+ :as="VuInput"
67
+ @keydown.home.end="handleHomeEnd"
68
+ />
69
+
70
+ <!-- list -->
71
+ <ComboboxContent class="overflow-y-auto overflow-x-hidden" :dismissable="false">
72
+ <div role="presentation">
73
+ <!-- empty -->
74
+ <ComboboxEmpty v-if="$slots.empty || emptyText">
75
+ <slot name="empty">
76
+ <div class="w-full text-center py-$xs" v-if="!!emptyText">
77
+ {{ emptyText }}
78
+ </div>
79
+ </slot>
80
+ </ComboboxEmpty>
81
+ <!-- group -->
82
+ <ComboboxGroup v-for="grp of groups" class="">
83
+ <ComboboxLabel v-if="grp.group" class="px-$m text-mt-$l text-mb-$m">
84
+ <span class="text-caption fw-bold">{{ grp.group }}</span>
85
+ </ComboboxLabel>
86
+ <!-- command item -->
87
+ <ComboboxItem
88
+ v-for="item of grp.items"
89
+ :value="item.value"
90
+ :icon="item.icon"
91
+ :label="item.label"
92
+ :selected="modelValue === item.value"
93
+ :as="VuMenuItem"
94
+ />
95
+ </ComboboxGroup>
96
+ <!-- -->
97
+ </div>
98
+ </ComboboxContent>
99
+ </ComboboxRoot>
100
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ icon?: string
4
+ selected?: boolean
5
+ label?: string
6
+ ariaSelected?: boolean
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <VuButton
12
+ :icon="icon"
13
+ :selected="selected"
14
+ :label="label"
15
+ :class="{
16
+ 'scope-grey': !selected,
17
+ }"
18
+ class="menu-item"
19
+ />
20
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as VuMenu } from './Menu.vue'
2
+ export { default as VuMenuItem } from './MenuItem.vue'
@@ -0,0 +1,6 @@
1
+ import { scFromObject } from '../../theme/utils/shortcut-obj'
2
+
3
+ export const menuShortcuts = {
4
+ 'menu-root': 'flex flex-col overflow-hidden',
5
+ 'menu-item': 'justify-start c8-flat gap-$m w-full fw-400',
6
+ }
@@ -0,0 +1,120 @@
1
+ <script setup lang="ts" generic="T">
2
+ const props = defineProps<{ items: T[]; maxVisible?: number }>()
3
+
4
+ const hiddenCount = ref(0)
5
+
6
+ const visibleItems = computed(() => {
7
+ return props.items.slice(0, props.items.length - hiddenCount.value)
8
+ })
9
+
10
+ const hiddenItems = computed(() => {
11
+ return props.items.slice(props.items.length - hiddenCount.value)
12
+ })
13
+
14
+ const root = ref<{ $el: HTMLDivElement }>()
15
+
16
+ let mObserver: MutationObserver
17
+ let rObserver: ResizeObserver
18
+
19
+ watch(
20
+ () => [props.items],
21
+ () => fit()
22
+ )
23
+
24
+ onMounted(() => {
25
+ mObserver = new MutationObserver(() => {
26
+ if (!busy) {
27
+ fit()
28
+ }
29
+ })
30
+ rObserver = new ResizeObserver(() => {
31
+ if (!busy) {
32
+ fit()
33
+ }
34
+ })
35
+ if (root.value) {
36
+ rObserver.observe(root.value.$el, {
37
+ box: 'content-box',
38
+ })
39
+ mObserver.observe(root.value.$el, {
40
+ childList: true,
41
+ subtree: true,
42
+ })
43
+ }
44
+ fit()
45
+ })
46
+
47
+ onBeforeUnmount(() => {
48
+ if (root.value) {
49
+ mObserver?.disconnect()
50
+ rObserver?.disconnect()
51
+ }
52
+ })
53
+
54
+ let busy = false
55
+
56
+ async function fit(i?: number) {
57
+ if (root.value && props.items.length > 1) {
58
+ busy = true
59
+ hiddenCount.value =
60
+ typeof i === 'number' ? i : Math.max(0, props.items.length - (props.maxVisible ?? 999))
61
+ await nextTick()
62
+
63
+ const rootWidth = root.value.$el.getBoundingClientRect().width
64
+
65
+ let itFits = false
66
+ let additional = 1
67
+ while (!itFits) {
68
+ itFits = true
69
+ const children = root.value.$el.children
70
+ const firstLeft = children[0]?.getBoundingClientRect().left
71
+ for (let j = 0; j < children.length; j++) {
72
+ const { left, width } = children[j].getBoundingClientRect()
73
+ const w = left + width - firstLeft
74
+ if (w >= rootWidth) {
75
+ itFits = false
76
+ hiddenCount.value =
77
+ props.items.length - (j + 1) + (hiddenCount.value > 0 ? additional : 1)
78
+ if (additional === 1) {
79
+ additional = 2
80
+ }
81
+ await nextTick()
82
+ break
83
+ }
84
+ }
85
+ }
86
+ busy = false
87
+
88
+ // if (!doesItFit()) {
89
+ // busy = true
90
+ // fit(hiddenCount.value + 1)
91
+ // } else {
92
+ // busy = false
93
+ // }
94
+ }
95
+ if (props.items.length < 2) {
96
+ hiddenCount.value = 0
97
+ }
98
+ }
99
+
100
+ function doesItFit() {
101
+ if (root.value) {
102
+ const c = root.value.$el
103
+ const { width } = c.getBoundingClientRect()
104
+ const overflowWidth = c.scrollWidth
105
+ return overflowWidth <= width
106
+ }
107
+ return true
108
+ }
109
+ </script>
110
+
111
+ <template>
112
+ <Primitive class="flex" ref="root">
113
+ <slot v-for="item of visibleItems" :item>
114
+ <span>{{ item }}</span>
115
+ </slot>
116
+ <slot v-if="hiddenCount > 0" name="overflow" :count="hiddenCount" :items="hiddenItems">
117
+ <span class="whitespace-nowrap">{{ hiddenCount }} more...</span>
118
+ </slot>
119
+ </Primitive>
120
+ </template>
@@ -0,0 +1 @@
1
+ export { default as VuOverflowContainer } from './OverflowContainer.vue'