@indielayer/ui 1.0.0-alpha.0 → 1.0.0-alpha.5

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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -72
  3. package/lib/components/avatar/Avatar.vue.d.ts +2 -2
  4. package/lib/components/badge/Badge.vue.d.ts +2 -2
  5. package/lib/components/button/Button.vue.d.ts +2 -2
  6. package/lib/components/button/ButtonGroup.vue.d.ts +2 -2
  7. package/lib/components/checkbox/Checkbox.vue.d.ts +4 -3
  8. package/lib/components/drawer/Drawer.vue.d.ts +2 -2
  9. package/lib/components/icon/Icon.vue.d.ts +7 -3
  10. package/lib/components/index.d.ts +2 -2
  11. package/lib/components/input/Input.vue.d.ts +3 -2
  12. package/lib/components/menu/Menu.vue.d.ts +2 -2
  13. package/lib/components/menu/MenuItem.vue.d.ts +3 -3
  14. package/lib/components/notifications/Notifications.vue.d.ts +2 -2
  15. package/lib/components/pagination/Pagination.vue.d.ts +3 -2
  16. package/lib/components/pagination/PaginationItem.vue.d.ts +2 -2
  17. package/lib/components/radio/Radio.vue.d.ts +2 -2
  18. package/lib/components/select/Select.vue.d.ts +3 -2
  19. package/lib/components/slider/Slider.vue.d.ts +2 -2
  20. package/lib/components/spacer/Spacer.vue.d.ts +1 -1
  21. package/lib/components/spinner/Spinner.vue.d.ts +2 -2
  22. package/lib/components/{tabs → tab}/Tab.vue.d.ts +2 -2
  23. package/lib/components/{tabs/Tabs.vue.d.ts → tab/TabGroup.vue.d.ts} +0 -0
  24. package/lib/components/table/TableBody.vue.d.ts +1 -1
  25. package/lib/components/table/TableHead.vue.d.ts +1 -1
  26. package/lib/components/tag/Tag.vue.d.ts +2 -2
  27. package/lib/components/textarea/Textarea.vue.d.ts +3 -11
  28. package/lib/components/toggle/Toggle.vue.d.ts +2 -2
  29. package/lib/composables/keys.d.ts +1 -0
  30. package/lib/create.d.ts +12 -0
  31. package/lib/index.cjs.js +2 -2
  32. package/lib/index.d.ts +2 -0
  33. package/lib/index.es.js +271 -130
  34. package/lib/install.d.ts +4 -6
  35. package/lib/nuxt.js +15 -16
  36. package/lib/nuxt.plugin.js +8 -0
  37. package/lib/style.css +1 -1
  38. package/lib/version.d.ts +1 -1
  39. package/package.json +21 -15
  40. package/src/components/alert/Alert.vue +164 -0
  41. package/src/components/avatar/Avatar.vue +137 -0
  42. package/src/components/badge/Badge.vue +107 -0
  43. package/src/components/breadcrumbs/Breadcrumbs.vue +60 -0
  44. package/src/components/button/Button.vue +433 -0
  45. package/src/components/button/ButtonGroup.vue +73 -0
  46. package/src/components/card/Card.vue +25 -0
  47. package/src/components/checkbox/Checkbox.vue +205 -0
  48. package/src/components/collapse/Collapse.vue +181 -0
  49. package/src/components/container/Container.vue +23 -0
  50. package/src/components/divider/Divider.vue +52 -0
  51. package/src/components/drawer/Drawer.vue +244 -0
  52. package/src/components/form/Form.vue +111 -0
  53. package/src/components/icon/Icon.vue +123 -0
  54. package/src/components/image/Image.vue +36 -0
  55. package/src/components/index.ts +45 -0
  56. package/src/components/input/Input.vue +199 -0
  57. package/src/components/link/Link.vue +110 -0
  58. package/src/components/menu/Menu.vue +118 -0
  59. package/src/components/menu/MenuItem.vue +277 -0
  60. package/src/components/modal/Modal.vue +175 -0
  61. package/src/components/notifications/Notifications.vue +318 -0
  62. package/src/components/pagination/Pagination.vue +181 -0
  63. package/src/components/pagination/PaginationItem.vue +58 -0
  64. package/src/components/popover/Popover.vue +194 -0
  65. package/src/components/popover/PopoverContainer.vue +23 -0
  66. package/src/components/progress/Progress.vue +86 -0
  67. package/src/components/radio/Radio.vue +220 -0
  68. package/src/components/scroll/Scroll.vue +143 -0
  69. package/src/components/select/Select.vue +408 -0
  70. package/src/components/skeleton/Skeleton.vue +23 -0
  71. package/src/components/slider/Slider.vue +240 -0
  72. package/src/components/spacer/Spacer.vue +11 -0
  73. package/src/components/spinner/Spinner.vue +45 -0
  74. package/src/components/tab/Tab.vue +100 -0
  75. package/src/components/tab/TabGroup.vue +151 -0
  76. package/src/components/table/Table.vue +172 -0
  77. package/src/components/table/TableBody.vue +13 -0
  78. package/src/components/table/TableCell.vue +78 -0
  79. package/src/components/table/TableHead.vue +15 -0
  80. package/src/components/table/TableHeader.vue +94 -0
  81. package/src/components/table/TableRow.vue +43 -0
  82. package/src/components/tag/Tag.vue +98 -0
  83. package/src/components/textarea/Textarea.vue +156 -0
  84. package/src/components/toggle/Toggle.vue +144 -0
  85. package/src/components/tooltip/Tooltip.vue +26 -0
  86. package/src/composables/colors-utils.ts +378 -0
  87. package/src/composables/colors.ts +82 -0
  88. package/src/composables/common.ts +20 -0
  89. package/src/composables/css.ts +45 -0
  90. package/src/composables/index.ts +7 -0
  91. package/src/composables/inputtable.ts +128 -0
  92. package/src/composables/interactive.ts +16 -0
  93. package/src/composables/keys.ts +8 -0
  94. package/src/composables/notification.ts +10 -0
  95. package/src/create.ts +38 -0
  96. package/src/exports/nuxt.js +32 -0
  97. package/src/exports/nuxt.plugin.js +8 -0
  98. package/src/exports/tailwind.preset.js +55 -0
  99. package/src/index.ts +8 -0
  100. package/src/install.ts +8 -0
  101. package/src/shims-vue.d.ts +6 -0
  102. package/src/version.ts +1 -0
  103. package/volar.d.ts +1 -1
@@ -0,0 +1,408 @@
1
+ <script lang="ts">
2
+ import { computed, defineComponent, ref, watch, type PropType } from 'vue'
3
+ import { useCSS } from '../../composables/css'
4
+ import { useCommon } from '../../composables/common'
5
+ import { useColors } from '../../composables/colors'
6
+ import { useInputtable } from '../../composables/inputtable'
7
+ import { useInteractive } from '../../composables/interactive'
8
+
9
+ import { useEventListener } from '@vueuse/core'
10
+
11
+ import XTag from '../../components/tag/Tag.vue'
12
+ import XMenuItem from '../../components/menu/MenuItem.vue'
13
+ import XSpinner from '../../components/spinner/Spinner.vue'
14
+ import XPopover from '../../components/popover/Popover.vue'
15
+ import XPopoverContainer from '../../components/popover/PopoverContainer.vue'
16
+
17
+ export type SelectOption = {
18
+ value: number | string,
19
+ disabled: boolean,
20
+ label: string
21
+ }
22
+
23
+ export default defineComponent({
24
+ name: 'XSelect',
25
+
26
+ components: {
27
+ XTag,
28
+ XMenuItem,
29
+ XSpinner,
30
+ XPopover,
31
+ XPopoverContainer,
32
+ },
33
+
34
+ validators: {
35
+ ...useCommon.validators(),
36
+ },
37
+
38
+ props: {
39
+ ...useCommon.props(),
40
+ ...useInteractive.props(),
41
+ ...useInputtable.props(),
42
+ placeholder: String,
43
+ options: Array as PropType<Array<SelectOption>>,
44
+ multiple: Boolean,
45
+ label: String,
46
+ helper: String,
47
+ flat: Boolean,
48
+ },
49
+
50
+ emits: useInputtable.emits(),
51
+
52
+ setup(props, { emit }) {
53
+ const elRef = ref<HTMLElement>()
54
+ const labelRef = ref()
55
+ const itemsRef = ref()
56
+ const popoverRef = ref()
57
+ const selectedIndex = ref<number | undefined>()
58
+
59
+ const interactive = useInteractive(elRef)
60
+
61
+ const checkIcon = '<path d="M5 13l4 4L19 7" />'
62
+
63
+ const selected = computed({
64
+ get(): any {
65
+ if (props.multiple) {
66
+ if (!props.modelValue) return []
67
+ if (Array.isArray(props.modelValue)) return props.modelValue
68
+ else return [props.modelValue]
69
+ }
70
+
71
+ return props.modelValue
72
+ },
73
+ set(value: string | number | []) {
74
+ emit('update:modelValue', value)
75
+ },
76
+ })
77
+
78
+ const internalOptions = computed(() => {
79
+ if (!props.options || props.options.length === 0) return []
80
+
81
+ return props.options.map((option) => {
82
+ let isActive = false
83
+
84
+ if (props.multiple && Array.isArray(selected.value)) {
85
+ // @ts-ignore
86
+ isActive = selected.value.includes(option.value)
87
+ } else {
88
+ isActive = option.value === selected.value
89
+ }
90
+
91
+ return {
92
+ value: option.value,
93
+ label: option.label,
94
+ active: isActive,
95
+ disabled: option.disabled,
96
+ iconRight: isActive ? checkIcon : undefined,
97
+ onClick: () => handleOptionClick(option.value),
98
+ }
99
+ })
100
+ })
101
+
102
+ const labelClasses = computed(() => {
103
+ if (props.size === 'xs') return 'text-xs'
104
+ else if (props.size === 'sm') return 'text-sm'
105
+ else if (props.size === 'lg') return 'text-lg'
106
+ else if (props.size === 'xl') return 'text-xl'
107
+
108
+ return ''
109
+ })
110
+
111
+ const sizeClasses = computed(() => {
112
+ if (props.size === 'xs') return 'px-2 py-1 text-xs'
113
+ else if (props.size === 'sm') return 'px-2 py-2 text-sm'
114
+ else if (props.size === 'lg') return 'px-4 py-3 text-lg'
115
+ else if (props.size === 'xl') return 'px-5 py-4 text-xl'
116
+
117
+ return 'px-3 py-2'
118
+ })
119
+
120
+ const css = useCSS('select')
121
+ const colors = useColors()
122
+ const color = colors.getPalette('primary')
123
+ const style = css.get('border', color[500])
124
+
125
+ const availableOptions = computed(() => props.options?.filter((option) => !option.disabled))
126
+
127
+ watch(() => popoverRef.value?.isOpen, () => {
128
+ if (popoverRef.value?.isOpen && (props.multiple || typeof selectedIndex.value === 'undefined'))
129
+ findSelectableIndex(-1)
130
+ })
131
+
132
+ watch(selectedIndex, (index) => {
133
+ if (typeof index !== 'undefined') itemsRef.value[index].$el.scrollIntoView({ block: 'nearest', inline: 'nearest' })
134
+ })
135
+
136
+ function findSelectableIndex(start: number | undefined, direction = 'down') {
137
+ if (!availableOptions.value || availableOptions.value.length === 0) {
138
+ selectedIndex.value = undefined
139
+
140
+ return
141
+ }
142
+
143
+ if (typeof start === 'undefined') {
144
+ start = direction === 'down' ? -1 : 1
145
+ }
146
+
147
+ if (direction === 'down') {
148
+ let next = start + 1
149
+
150
+ if (next > internalOptions.value.length - 1) next = 0
151
+ while (internalOptions.value[next].disabled) {
152
+ if (++next > internalOptions.value.length - 1) next = 0
153
+ }
154
+ selectedIndex.value = next
155
+ } else {
156
+ let next = start - 1
157
+
158
+ if (next < 0) next = internalOptions.value.length - 1
159
+ while (internalOptions.value[next].disabled) {
160
+ if (--next < 0) next = internalOptions.value.length - 1
161
+ }
162
+ selectedIndex.value = next
163
+ }
164
+ }
165
+
166
+ useEventListener(labelRef, 'keydown', handleKeydown)
167
+
168
+ function handleKeydown(e: KeyboardEvent) {
169
+ if (internalOptions.value.length === 0) return
170
+
171
+ if (e.code === 'ArrowDown') {
172
+ e.preventDefault()
173
+ if (!popoverRef.value.isOpen) {
174
+ popoverRef.value.open()
175
+
176
+ return
177
+ }
178
+ findSelectableIndex(selectedIndex.value, 'down')
179
+ } else if (e.code === 'ArrowUp') {
180
+ e.preventDefault()
181
+ if (!popoverRef.value.isOpen) return
182
+ findSelectableIndex(selectedIndex.value, 'up')
183
+ } else if (e.code === 'Enter' || e.code === 'Space') {
184
+ e.preventDefault()
185
+ e.stopPropagation()
186
+ if (!popoverRef.value.isOpen) {
187
+ popoverRef.value.open()
188
+
189
+ return
190
+ }
191
+ if (typeof selectedIndex.value !== 'undefined') {
192
+ handleOptionClick(internalOptions.value[selectedIndex.value].value)
193
+ if (!props.multiple) popoverRef.value.close()
194
+ }
195
+ } else if (e.code === 'Escape') {
196
+ e.preventDefault()
197
+ e.stopPropagation()
198
+ popoverRef.value.close()
199
+ } else if (e.code === 'Tab') {
200
+ popoverRef.value.close()
201
+ }
202
+ }
203
+
204
+ function handleOptionClick(value: string | number) {
205
+ const option = props.options?.find((i) => i.value === value)
206
+
207
+ if (!option || option.disabled) return
208
+
209
+ if (props.multiple) {
210
+ if (Array.isArray(selected.value)) {
211
+ // @ts-ignore
212
+ const index = selected.value.indexOf(value)
213
+
214
+ if (index !== -1) selected.value.splice(index, 1)
215
+ else {
216
+ // @ts-ignore
217
+ selected.value.push(value)
218
+ emit('update:modelValue', selected.value)
219
+ }
220
+ } else {
221
+ // @ts-ignore
222
+ selected.value = [value]
223
+ }
224
+ } else {
225
+ selected.value = value
226
+ }
227
+ }
228
+
229
+ function isEmpty(value: string | number | []) {
230
+ if (typeof value === 'undefined' || value === null) return true
231
+ if (value === '') return true
232
+ if (Array.isArray(value) && value.length === 0) return true
233
+ if (!Array.isArray(value) && typeof value === 'object' && Object.keys(value).length === 0) return true
234
+
235
+ return false
236
+ }
237
+
238
+ function handleRemove(e: Event, value: string) {
239
+ e.stopPropagation()
240
+
241
+ // find value in selected and remove it
242
+ // @ts-ignore
243
+ const index = selected.value.indexOf(value)
244
+
245
+ if (index !== -1) {
246
+ // @ts-ignore
247
+ selected.value.splice(index, 1)
248
+ emit('update:modelValue', selected.value)
249
+ }
250
+ }
251
+
252
+ function getLabel(value: string | number | []) {
253
+ const option = props.options?.find((i) => i.value === value)
254
+
255
+ if (option) return option.label
256
+
257
+ return ''
258
+ }
259
+
260
+ return {
261
+ ...interactive,
262
+ ...useInputtable(props, { focus: interactive.focus, emit, withListeners: false }),
263
+ elRef,
264
+ labelRef,
265
+ itemsRef,
266
+ popoverRef,
267
+ selected,
268
+ selectedIndex,
269
+ internalOptions,
270
+ labelClasses,
271
+ sizeClasses,
272
+ style,
273
+ isEmpty,
274
+ getLabel,
275
+ handleRemove,
276
+ }
277
+ },
278
+ })
279
+ </script>
280
+
281
+ <template>
282
+ <label
283
+ ref="labelRef"
284
+ tabindex="0"
285
+ class="group relative inline-block align-bottom text-left focus:outline-none"
286
+ :class="[{ 'mb-3': isInsideForm }]"
287
+ >
288
+ <p
289
+ v-if="label"
290
+ class="font-medium text-gray-800 dark:text-gray-200 mb-1"
291
+ :class="labelClasses"
292
+ v-text="label"
293
+ ></p>
294
+ <div class="relative">
295
+ <x-popover ref="popoverRef" block :disabled="disabled || loading" :dismiss-on-click="!multiple">
296
+ <div
297
+ class="w-full border border-gray-300 hover:border-gray-400 dark:border-gray-700 pr-8 transition-colors duration-150 ease-in-out rounded-md shadow-sm
298
+ group-focus:border-[color:var(--x-select-border)]
299
+ "
300
+ :style="style"
301
+ :class="[
302
+ sizeClasses,
303
+ disabled
304
+ ? 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed'
305
+ : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200',
306
+ {
307
+ // error
308
+ 'border-red-500 focus:border-red-500 dark:focus:border-red-500': errorInternal,
309
+ },
310
+ ]"
311
+ >
312
+ <template v-if="multiple && Array.isArray(selected) && selected.length > 0">
313
+ <x-tag
314
+ v-for="value in selected"
315
+ :key="value"
316
+ size="sm"
317
+ class="mr-1"
318
+ removable
319
+ @remove="(e) => { handleRemove(e, value) }"
320
+ >{{ getLabel(value) }}</x-tag>
321
+ </template>
322
+ <template v-else-if="!multiple && !isEmpty(selected)">
323
+ {{ getLabel(selected) }}
324
+ </template>
325
+
326
+ <template v-else>
327
+ <div
328
+ v-if="placeholder"
329
+ class="text-gray-400 dark:text-gray-500"
330
+ >
331
+ {{ placeholder }}
332
+ </div>
333
+ <div v-else>&nbsp;</div>
334
+ </template>
335
+ </div>
336
+
337
+ <template #content>
338
+ <x-popover-container class="py-1 max-h-72 overflow-scroll">
339
+ <template v-if="internalOptions.length > 0">
340
+ <x-menu-item
341
+ v-for="(item, index) in internalOptions"
342
+ :key="index"
343
+ ref="itemsRef"
344
+ :item="item"
345
+ :size="size"
346
+ :disabled="item.disabled"
347
+ :selected="index === selectedIndex"
348
+ color="primary"
349
+ filled
350
+ />
351
+ </template>
352
+ <div v-else class="px-2 text-center text-gray-400">
353
+ No options
354
+ </div>
355
+ </x-popover-container>
356
+ </template>
357
+ </x-popover>
358
+
359
+ <select
360
+ ref="elRef"
361
+ v-model="selected"
362
+ class="hidden"
363
+ :name="name"
364
+ :disabled="disabled || loading"
365
+ :multiple="multiple"
366
+ :readonly="readonly"
367
+ :value="modelValue"
368
+ v-on="inputListeners"
369
+ >
370
+ <option
371
+ v-for="(option, index) in options"
372
+ :key="index"
373
+ :value="option.value"
374
+ :disabled="option.disabled"
375
+ >
376
+ {{ option.label }}
377
+ </option>
378
+ </select>
379
+
380
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2">
381
+ <x-spinner v-if="loading" :size="size" />
382
+ <svg
383
+ v-else
384
+ class="stroke-2"
385
+ :class="[
386
+ disabled ? 'text-gray-600 dark:text-gray-500': 'text-gray-500 dark:text-gray-400',
387
+ {
388
+ 'h-3 w-3': size === 'sm' || size === 'xs',
389
+ 'h-5 w-5': !size || !['xs', 'sm', 'lg', 'xl'].includes(size),
390
+ 'h-6 w-6': size === 'lg',
391
+ 'h-7 w-7': size === 'xl',
392
+ }
393
+ ]"
394
+ viewBox="0 0 24 24"
395
+ stroke="currentColor"
396
+ stroke-linejoin="round"
397
+ stroke-linecap="round"
398
+ fill="none"
399
+ >
400
+ <path d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
401
+ </svg>
402
+ </div>
403
+ </div>
404
+
405
+ <p v-if="errorInternal" class="text-sm text-red-500 mt-1" v-text="errorInternal"></p>
406
+ <p v-else-if="helper" class="text-sm text-gray-500 mt-1" v-text="helper"></p>
407
+ </label>
408
+ </template>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue'
3
+
4
+ export default defineComponent({
5
+ name: 'XSkeleton',
6
+
7
+ props: {
8
+ tag: {
9
+ type: String,
10
+ default: 'div',
11
+ },
12
+ },
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <component
18
+ :is="tag"
19
+ class="animate-pulse bg-gray-300 dark:bg-gray-600 rounded-md"
20
+ >
21
+ &#8203;
22
+ </component>
23
+ </template>
@@ -0,0 +1,240 @@
1
+ <script lang="ts">
2
+ import { computed, defineComponent, ref, watch } from 'vue'
3
+ import { useCSS } from '../../composables/css'
4
+ import { useCommon } from '../../composables/common'
5
+ import { useColors } from '../../composables/colors'
6
+ import { useInteractive } from '../../composables/interactive'
7
+ import { useInputtable } from '../../composables/inputtable'
8
+
9
+ import { useEventListener } from '@vueuse/core'
10
+
11
+ import XProgress from '../../components/progress/Progress.vue'
12
+
13
+ export default defineComponent({
14
+ name: 'XSlider',
15
+
16
+ components: {
17
+ XProgress,
18
+ },
19
+
20
+ validators: {
21
+ ...useCommon.validators(),
22
+ },
23
+
24
+ props: {
25
+ ...useCommon.props(),
26
+ ...useColors.props('primary'),
27
+ ...useInteractive.props(),
28
+ ...useInputtable.props(),
29
+ label: String,
30
+ min: Number,
31
+ max: Number,
32
+ step: {
33
+ type: Number,
34
+ default: 1,
35
+ },
36
+ gradient: Boolean,
37
+ },
38
+
39
+ emits: useInputtable.emits(false),
40
+
41
+ setup(props, { emit }) {
42
+ const elRef = ref<HTMLElement>()
43
+ const dragRef = ref<HTMLElement>()
44
+ const progressRef = ref<HTMLElement>()
45
+ const draggingElement = ref<HTMLElement>()
46
+ const value = ref<number>(Number(props.modelValue ?? 0))
47
+
48
+ const interactive = useInteractive(elRef)
49
+
50
+ const labelClasses = computed(() => {
51
+ if (props.size === 'xs') return 'text-xs'
52
+ else if (props.size === 'sm') return 'text-sm'
53
+ else if (props.size === 'lg') return 'text-lg'
54
+ else if (props.size === 'xl') return 'text-xl'
55
+
56
+ return ''
57
+ })
58
+
59
+ const css = useCSS('slider')
60
+ const colors = useColors()
61
+ const primary = colors.getPalette('primary')
62
+ const style = computed(() => {
63
+ const color = colors.getPalette(props.color)
64
+
65
+ return css.variables({
66
+ bg: color[500],
67
+ border: primary[500],
68
+ // width: value.value + '%',
69
+ })
70
+ })
71
+
72
+ watch(() => props.modelValue, (val) => {
73
+ value.value = Number(val ?? 0)
74
+ })
75
+
76
+ watch(value, (val) => {
77
+ emit('update:modelValue', val)
78
+ })
79
+
80
+ const initial = ref()
81
+ const position = ref()
82
+ const isDragging = computed(() => !!initial.value)
83
+
84
+ function startProgressDrag(e: PointerEvent) {
85
+ e.stopPropagation()
86
+ if (!dragRef.value || !progressRef.value) return
87
+
88
+ interactive.focus()
89
+
90
+ const maxWidth = progressRef.value.offsetWidth
91
+ let percent = Math.floor(e.offsetX * 100 / maxWidth)
92
+
93
+ console.log(e, e.offsetX, percent)
94
+
95
+ if (percent < 0) percent = 0
96
+ if (percent > 100) percent = 100
97
+
98
+ value.value = percent
99
+
100
+ setTimeout(() => {
101
+ initial.value = {
102
+ x: e.x,
103
+ y: e.y,
104
+ maxWidth,
105
+ offsetX: dragRef.value?.offsetLeft,
106
+ }
107
+ })
108
+ e.preventDefault()
109
+ e.stopPropagation()
110
+ }
111
+
112
+ function startDrag(e: PointerEvent) {
113
+ if (!dragRef.value || !progressRef.value) return
114
+
115
+ interactive.focus()
116
+
117
+ initial.value = {
118
+ x: e.x,
119
+ y: e.y,
120
+ maxWidth: progressRef.value.offsetWidth,
121
+ offsetX: dragRef.value.offsetLeft,
122
+ }
123
+
124
+ e.preventDefault()
125
+ e.stopPropagation()
126
+ }
127
+
128
+ function moveDrag(e: PointerEvent) {
129
+ if (!initial.value || !dragRef.value) return
130
+
131
+ const { x, maxWidth, offsetX } = initial.value
132
+ const movedX = e.x - x
133
+
134
+ if (movedX === 0) return
135
+
136
+ const newMoveX = offsetX + movedX
137
+ let percent = Math.floor(newMoveX * 100 / maxWidth)
138
+
139
+ if (percent < 0) percent = 0
140
+ if (percent > 100) percent = 100
141
+
142
+ value.value = percent
143
+
144
+ e.preventDefault()
145
+ e.stopPropagation()
146
+ }
147
+
148
+ function endDrag(e: PointerEvent) {
149
+ if (!initial.value) return
150
+ initial.value = undefined
151
+ e.preventDefault()
152
+ e.stopPropagation()
153
+ }
154
+
155
+ useEventListener(progressRef, 'pointerdown', startProgressDrag, false)
156
+ useEventListener(dragRef, 'pointerdown', startDrag, false)
157
+ useEventListener(window, 'pointermove', moveDrag, true)
158
+ useEventListener(window, 'pointerup', endDrag, true)
159
+
160
+ useEventListener(elRef, 'keydown', handleKeydown)
161
+
162
+ function handleKeydown(e: KeyboardEvent) {
163
+ if (e.code === 'ArrowLeft') {
164
+ const nextValue = value.value - 1
165
+
166
+ if (nextValue >= 0) value.value = nextValue
167
+
168
+ e.preventDefault()
169
+ } else if (e.code === 'ArrowRight') {
170
+ const nextValue = value.value + 1
171
+
172
+ if (nextValue <= 100) value.value = nextValue
173
+
174
+ e.preventDefault()
175
+ }
176
+ }
177
+
178
+ return {
179
+ ...interactive,
180
+ ...useInputtable(props, { focus: interactive.focus, emit, withListeners: false }),
181
+ labelClasses,
182
+ draggingElement,
183
+ value,
184
+ elRef,
185
+ dragRef,
186
+ progressRef,
187
+ style,
188
+ isDragging,
189
+ position,
190
+ }
191
+ },
192
+ })
193
+ </script>
194
+
195
+ <template>
196
+ <label
197
+ ref="elRef"
198
+ tabindex="0"
199
+ class="group inline-block relative align-bottom text-left focus:outline-none"
200
+ :class="{ 'mb-3': isInsideForm }"
201
+ :style="style"
202
+ >
203
+ <p
204
+ v-if="label"
205
+ class="font-medium text-gray-800 dark:text-gray-200 mb-1"
206
+ :class="labelClasses"
207
+ v-text="label"
208
+ ></p>
209
+
210
+ <div class="flex items-center relative w-full" >
211
+ <slot name="prefix" :value="value"></slot>
212
+ <div
213
+ ref="progressRef"
214
+ :class="[isDragging ? 'cursor-grabbing' : 'cursor-grab']"
215
+ class="relative w-full py-2 mx-2"
216
+ >
217
+ <div class="-mx-2">
218
+ <x-progress
219
+ :percentage="value"
220
+ :animate="false"
221
+ thick
222
+ class="w-full"
223
+ :gradient="gradient"
224
+ />
225
+ </div>
226
+ <div
227
+ ref="dragRef"
228
+ class="absolute w-[20px] h-[20px] -mt-[13px] -ml-[10px] rounded-full bg-white border shadow-sm
229
+ group-focus:border-[color:var(--x-slider-border)]
230
+ "
231
+ :class="[isDragging ? 'cursor-grabbing' : 'cursor-grab']"
232
+ :style="`left: ${value}%;`"
233
+ ></div>
234
+ </div>
235
+ <slot name="suffix" :value="value"></slot>
236
+ </div>
237
+
238
+ <p v-if="errorInternal" class="text-sm text-red-500 mt-1" v-text="errorInternal"></p>
239
+ </label>
240
+ </template>
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue'
3
+
4
+ export default defineComponent({
5
+ name: 'XSpacer',
6
+ })
7
+ </script>
8
+
9
+ <template>
10
+ <div class="flex-grow"></div>
11
+ </template>