poe-svelte-ui-lib 1.2.25 → 1.2.27
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/Accordion/AccordionProps.svelte +11 -4
- package/dist/Button/ButtonProps.svelte +22 -21
- package/dist/ColorPicker/ColorPickerProps.svelte +16 -8
- package/dist/FileAttach/FileAttach.svelte +4 -10
- package/dist/FileAttach/FileAttachProps.svelte +2 -0
- package/dist/Graph/GraphProps.svelte +2 -1
- package/dist/Input/InputProps.svelte +18 -5
- package/dist/Joystick/Joystick.svelte +1 -2
- package/dist/Map/Map.svelte +205 -0
- package/dist/Map/Map.svelte.d.ts +4 -0
- package/dist/Map/MapProps.svelte +157 -0
- package/dist/Map/MapProps.svelte.d.ts +11 -0
- package/dist/Select/SelectProps.svelte +27 -23
- package/dist/Slider/Slider.svelte +149 -187
- package/dist/Slider/SliderProps.svelte +16 -1
- package/dist/Switch/SwitchProps.svelte +23 -6
- package/dist/Table/Table.svelte +2 -2
- package/dist/Table/TableProps.svelte +32 -6
- package/dist/TextField/TextFieldProps.svelte +12 -5
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/locales/translations.js +6 -6
- package/dist/types.d.ts +20 -2
- package/package.json +6 -5
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext } from 'svelte'
|
|
3
|
+
import { t } from '../locales/i18n'
|
|
4
|
+
import { type UIComponent, type IGraphProps, updateProperty } from '../types'
|
|
5
|
+
import * as UI from '..'
|
|
6
|
+
import Modal from '../Modal.svelte'
|
|
7
|
+
import { ICONS } from '../icons'
|
|
8
|
+
import Button from '../Button/Button.svelte'
|
|
9
|
+
import CrossIcon from '../libIcons/CrossIcon.svelte'
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
component,
|
|
13
|
+
onPropertyChange,
|
|
14
|
+
forConstructor = true,
|
|
15
|
+
} = $props<{
|
|
16
|
+
component: UIComponent & { properties: Partial<IGraphProps> }
|
|
17
|
+
onPropertyChange: (value?: string | object, name?: string, access?: string) => void
|
|
18
|
+
forConstructor?: boolean
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
let showIconLib = $state(false)
|
|
22
|
+
|
|
23
|
+
const DeviceVariables = getContext<{ id: string; value: string; name: string }[]>('DeviceVariables')
|
|
24
|
+
let VARIABLE_OPTIONS = $derived(DeviceVariables && Array.isArray(DeviceVariables) ? DeviceVariables : [])
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
{#if forConstructor}
|
|
28
|
+
<div class="relative flex flex-row items-start justify-center">
|
|
29
|
+
<!-- Сообщение для отправки в ws по нажатию кнопки -->
|
|
30
|
+
<div class="flex w-1/3 flex-col items-center px-2">
|
|
31
|
+
<UI.Select
|
|
32
|
+
label={{ name: $t('constructor.props.variable') }}
|
|
33
|
+
options={VARIABLE_OPTIONS}
|
|
34
|
+
value={VARIABLE_OPTIONS.find((opt) => opt.value === component.properties.id)}
|
|
35
|
+
onUpdate={(value) => {
|
|
36
|
+
updateProperty('id', value.value as string, component, onPropertyChange)
|
|
37
|
+
updateProperty('eventHandler.Variables', value.value as string, component, onPropertyChange)
|
|
38
|
+
onPropertyChange(null, value.name?.split('—')[1].trim(), null)
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
43
|
+
<UI.Input
|
|
44
|
+
label={{ name: $t('constructor.props.label') }}
|
|
45
|
+
value={component.properties.label.name}
|
|
46
|
+
onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
|
|
47
|
+
/>
|
|
48
|
+
<UI.Input
|
|
49
|
+
label={{ name: $t('constructor.props.label.class') }}
|
|
50
|
+
value={component.properties.label.class}
|
|
51
|
+
onUpdate={(value) => updateProperty('label.class', value as string, component, onPropertyChange)}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
55
|
+
<div class="mt-6 flex gap-2">
|
|
56
|
+
<UI.Button content={{ name: $t('constructor.props.markerIcon') }} onClick={() => (showIconLib = true)} />
|
|
57
|
+
{#if showIconLib}
|
|
58
|
+
<Modal bind:isOpen={showIconLib} wrapperClass="w-130">
|
|
59
|
+
{#snippet main()}
|
|
60
|
+
<div class="grid grid-cols-3">
|
|
61
|
+
{#each ICONS as category}
|
|
62
|
+
<div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
|
|
63
|
+
<div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
|
|
64
|
+
<div class="grid grid-cols-3 place-items-center gap-2">
|
|
65
|
+
{#each category[1] as icon}
|
|
66
|
+
<button
|
|
67
|
+
class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
|
|
68
|
+
onclick={() => {
|
|
69
|
+
updateProperty('markerIcon', icon as string, component, onPropertyChange)
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{@html icon}
|
|
73
|
+
</button>{/each}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
{/each}
|
|
77
|
+
</div>
|
|
78
|
+
{/snippet}
|
|
79
|
+
</Modal>
|
|
80
|
+
{/if}
|
|
81
|
+
{#if component.properties.markerIcon}
|
|
82
|
+
<Button
|
|
83
|
+
wrapperClass="w-8.5 "
|
|
84
|
+
componentClass="p-0.5 bg-red"
|
|
85
|
+
content={{ icon: CrossIcon }}
|
|
86
|
+
onClick={() => {
|
|
87
|
+
updateProperty('markerIcon', '', component, onPropertyChange)
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
{:else}
|
|
95
|
+
<div class="relative mb-2 flex flex-row items-start justify-center">
|
|
96
|
+
<!-- Сообщение для отправки в ws по нажатию кнопки -->
|
|
97
|
+
<div class="flex w-1/3 flex-col items-center px-2">
|
|
98
|
+
<UI.Input
|
|
99
|
+
label={{ name: $t('constructor.props.id') }}
|
|
100
|
+
value={component.properties.id}
|
|
101
|
+
onUpdate={(value) => updateProperty('id', value as string, component, onPropertyChange)}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
105
|
+
<UI.Input
|
|
106
|
+
label={{ name: $t('constructor.props.label') }}
|
|
107
|
+
value={component.properties.label.name}
|
|
108
|
+
onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
|
|
109
|
+
/>
|
|
110
|
+
<UI.Input
|
|
111
|
+
label={{ name: $t('constructor.props.label.class') }}
|
|
112
|
+
value={component.properties.label.class}
|
|
113
|
+
onUpdate={(value) => updateProperty('label.class', value as string, component, onPropertyChange)}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
118
|
+
<div class="mt-6 flex gap-2">
|
|
119
|
+
<UI.Button content={{ name: $t('constructor.props.markerIcon') }} onClick={() => (showIconLib = true)} />
|
|
120
|
+
{#if showIconLib}
|
|
121
|
+
<Modal bind:isOpen={showIconLib} wrapperClass="w-130">
|
|
122
|
+
{#snippet main()}
|
|
123
|
+
<div class="grid grid-cols-3">
|
|
124
|
+
{#each ICONS as category}
|
|
125
|
+
<div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
|
|
126
|
+
<div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
|
|
127
|
+
<div class="grid grid-cols-3 place-items-center gap-2">
|
|
128
|
+
{#each category[1] as icon}
|
|
129
|
+
<button
|
|
130
|
+
class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
|
|
131
|
+
onclick={() => {
|
|
132
|
+
updateProperty('markerIcon', icon as string, component, onPropertyChange)
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
{@html icon}
|
|
136
|
+
</button>{/each}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
{/each}
|
|
140
|
+
</div>
|
|
141
|
+
{/snippet}
|
|
142
|
+
</Modal>
|
|
143
|
+
{/if}
|
|
144
|
+
{#if component.properties.markerIcon}
|
|
145
|
+
<Button
|
|
146
|
+
wrapperClass="w-8.5 "
|
|
147
|
+
componentClass="p-0.5 bg-red"
|
|
148
|
+
content={{ icon: CrossIcon }}
|
|
149
|
+
onClick={() => {
|
|
150
|
+
updateProperty('markerIcon', '', component, onPropertyChange)
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
{/if}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
{/if}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type UIComponent, type IGraphProps } from '../types';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
component: UIComponent & {
|
|
4
|
+
properties: Partial<IGraphProps>;
|
|
5
|
+
};
|
|
6
|
+
onPropertyChange: (value?: string | object, name?: string, access?: string) => void;
|
|
7
|
+
forConstructor?: boolean;
|
|
8
|
+
};
|
|
9
|
+
declare const MapProps: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type MapProps = ReturnType<typeof MapProps>;
|
|
11
|
+
export default MapProps;
|
|
@@ -105,6 +105,13 @@
|
|
|
105
105
|
/>
|
|
106
106
|
</div>
|
|
107
107
|
<div class="flex w-1/3 flex-col items-center px-2">
|
|
108
|
+
<UI.Select
|
|
109
|
+
label={{ name: $t('constructor.props.access') }}
|
|
110
|
+
type="buttons"
|
|
111
|
+
options={$optionsStore.ACCESS_OPTION}
|
|
112
|
+
value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
|
|
113
|
+
onUpdate={(option) => onPropertyChange(null, null, option.value)}
|
|
114
|
+
/>
|
|
108
115
|
<UI.Select
|
|
109
116
|
label={{ name: $t('constructor.props.type') }}
|
|
110
117
|
type="buttons"
|
|
@@ -131,30 +138,20 @@
|
|
|
131
138
|
}}
|
|
132
139
|
/>
|
|
133
140
|
{#if component.properties.bitMode}
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
type="number"
|
|
145
|
-
/>
|
|
146
|
-
<UI.Input
|
|
147
|
-
label={{ name: $t('constructor.props.range.end') }}
|
|
148
|
-
value={component.properties.range.end}
|
|
149
|
-
onUpdate={(value) => {
|
|
150
|
-
updateProperty('range.end', value as number, component, onPropertyChange)
|
|
141
|
+
<UI.Slider
|
|
142
|
+
label={{ name: $t('constructor.props.range') }}
|
|
143
|
+
type="range"
|
|
144
|
+
number={{ minNum: 0, maxNum: 31, step: 1 }}
|
|
145
|
+
value={[component.properties.range.start, component.properties.range.end]}
|
|
146
|
+
onUpdate={(value) => {
|
|
147
|
+
if (Array.isArray(value)) {
|
|
148
|
+
if (value[1] - value[0] > 6) value = [value[0], value[0] + 6]
|
|
149
|
+
updateProperty('range.start', value[0] as number, component, onPropertyChange)
|
|
150
|
+
updateProperty('range.end', value[1] as number, component, onPropertyChange)
|
|
151
151
|
generateBitOptions(component.properties.range.start, component.properties.range.end)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
type="number"
|
|
156
|
-
/>
|
|
157
|
-
</div>
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
/>
|
|
158
155
|
{/if}
|
|
159
156
|
</div>
|
|
160
157
|
<div class="flex w-1/3 flex-col items-center px-2">
|
|
@@ -271,6 +268,13 @@
|
|
|
271
268
|
value={component.properties.id}
|
|
272
269
|
onUpdate={(value) => updateProperty('id', value as string, component, onPropertyChange)}
|
|
273
270
|
/>
|
|
271
|
+
<UI.Select
|
|
272
|
+
label={{ name: $t('constructor.props.access') }}
|
|
273
|
+
type="buttons"
|
|
274
|
+
options={$optionsStore.ACCESS_OPTION}
|
|
275
|
+
value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
|
|
276
|
+
onUpdate={(option) => onPropertyChange(null, null, option.value)}
|
|
277
|
+
/>
|
|
274
278
|
<UI.Input
|
|
275
279
|
label={{ name: $t('constructor.props.wrapperclass') }}
|
|
276
280
|
value={component.properties.wrapperClass}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
<!-- $lib/ElementsUI/Slider.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import type { ISliderProps } from '../types'
|
|
4
|
-
import IconGripVerticalLeft from '../libIcons/IconGripVerticalLeft.svelte'
|
|
5
|
-
import IconGripVerticalRight from '../libIcons/IconGripVerticalRight.svelte'
|
|
6
|
-
import IconGripVerticalDual from '../libIcons/IconGripVerticalDual.svelte'
|
|
7
4
|
import { twMerge } from 'tailwind-merge'
|
|
5
|
+
import { onDestroy, onMount } from 'svelte'
|
|
8
6
|
|
|
9
7
|
let {
|
|
10
8
|
id = crypto.randomUUID(),
|
|
@@ -27,10 +25,9 @@
|
|
|
27
25
|
let lowerValue = $derived(isRange && Array.isArray(value) ? value[0] : number.minNum)
|
|
28
26
|
let upperValue = $derived(isRange && Array.isArray(value) ? value[1] : number.maxNum)
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const upperPosition = $derived(((upperValue - number.minNum) / (number.maxNum - number.minNum)) * 100)
|
|
28
|
+
let activeRound: 'floor' | 'ceil' = $state('floor')
|
|
29
|
+
|
|
30
|
+
let centerNum = $derived(lowerValue + Math[activeRound]((upperValue - lowerValue) / 2 / number.step) * number.step)
|
|
34
31
|
|
|
35
32
|
$effect(() => {
|
|
36
33
|
if (value === undefined || value === null) {
|
|
@@ -43,13 +40,15 @@
|
|
|
43
40
|
const stepValue = direction === 'increment' ? number.step : -number.step
|
|
44
41
|
if (isRange && target !== 'single') {
|
|
45
42
|
if (target === 'lower') {
|
|
46
|
-
lowerValue = Math.max(number.minNum, Math.min(lowerValue + stepValue, upperValue))
|
|
43
|
+
lowerValue = roundToClean(Math.max(number.minNum, Math.min(lowerValue + stepValue, upperValue)))
|
|
44
|
+
lowerValue = roundToClean(lowerValue == upperValue ? upperValue - number.step : lowerValue)
|
|
47
45
|
} else {
|
|
48
|
-
upperValue = Math.min(number.maxNum, Math.max(upperValue + stepValue, lowerValue))
|
|
46
|
+
upperValue = roundToClean(Math.min(number.maxNum, Math.max(upperValue + stepValue, lowerValue)))
|
|
47
|
+
upperValue = roundToClean(upperValue == lowerValue ? upperValue + number.step : upperValue)
|
|
49
48
|
}
|
|
50
49
|
onUpdate([lowerValue, upperValue])
|
|
51
50
|
} else {
|
|
52
|
-
singleValue = Math.max(number.minNum, Math.min(singleValue + stepValue, number.maxNum))
|
|
51
|
+
singleValue = roundToClean(Math.max(number.minNum, Math.min(singleValue + stepValue, number.maxNum)))
|
|
53
52
|
onUpdate(singleValue)
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -63,36 +62,20 @@
|
|
|
63
62
|
}
|
|
64
63
|
})
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
e.stopPropagation()
|
|
69
|
-
const track = e.currentTarget as HTMLElement
|
|
70
|
-
const rect = track.getBoundingClientRect()
|
|
71
|
-
const clickPercent = ((e.clientX - rect.left) / rect.width) * 100
|
|
72
|
-
const rawValue = number.minNum + (clickPercent / 100) * (number.maxNum - number.minNum)
|
|
73
|
-
const clickValue = Math.round((rawValue - number.minNum) / number.step) * number.step + number.minNum
|
|
65
|
+
const roundToClean = (num: number): number => {
|
|
66
|
+
if (Number.isInteger(num)) return num
|
|
74
67
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const upperDiff = Math.abs(clickValue - upperValue)
|
|
68
|
+
const rounded1 = Number(num.toFixed(1))
|
|
69
|
+
if (Math.abs(rounded1 - num) < 1e-10) return rounded1
|
|
78
70
|
|
|
79
|
-
|
|
71
|
+
const rounded2 = Number(num.toFixed(2))
|
|
72
|
+
if (Math.abs(rounded2 - num) < 1e-10) return rounded2
|
|
80
73
|
|
|
81
|
-
|
|
82
|
-
lowerValue = Math.max(number.minNum, Math.min(clickValue, upperValue))
|
|
83
|
-
} else {
|
|
84
|
-
upperValue = Math.min(number.maxNum, Math.max(clickValue, lowerValue))
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
onUpdate([lowerValue, upperValue])
|
|
88
|
-
} else {
|
|
89
|
-
singleValue = Math.max(number.minNum, Math.min(clickValue, number.maxNum))
|
|
90
|
-
onUpdate(singleValue)
|
|
91
|
-
}
|
|
74
|
+
return rounded2
|
|
92
75
|
}
|
|
93
76
|
</script>
|
|
94
77
|
|
|
95
|
-
<div class={twMerge(`relative flex w-full flex-col items-center `, wrapperClass)}>
|
|
78
|
+
<div class={twMerge(`bg-blue relative flex w-full flex-col items-center `, wrapperClass)}>
|
|
96
79
|
{#if label.name}
|
|
97
80
|
<h5 class={twMerge(`w-full px-4 text-center`, label.class)}>{label.name}</h5>
|
|
98
81
|
{/if}
|
|
@@ -100,109 +83,149 @@
|
|
|
100
83
|
<!-- Слайдер -->
|
|
101
84
|
<div
|
|
102
85
|
id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
|
|
103
|
-
class="relative flex h-
|
|
86
|
+
class="relative flex h-8 w-full items-center justify-center rounded-full {disabled ? 'cursor-not-allowed opacity-50' : ''}"
|
|
104
87
|
>
|
|
105
88
|
{#if isRange}
|
|
106
|
-
|
|
107
|
-
<div
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
89
|
+
{@const userAgent = navigator.userAgent}
|
|
90
|
+
<div class="flex w-full">
|
|
91
|
+
<input
|
|
92
|
+
type="range"
|
|
93
|
+
min={number.minNum}
|
|
94
|
+
max={centerNum}
|
|
95
|
+
step={number.step}
|
|
96
|
+
bind:value={lowerValue}
|
|
97
|
+
oninput={disabled
|
|
98
|
+
? undefined
|
|
99
|
+
: (e) => {
|
|
100
|
+
const newValue = Math.min(Number((e.target as HTMLInputElement).value), upperValue)
|
|
101
|
+
lowerValue = roundToClean(newValue == upperValue ? upperValue - number.step : newValue)
|
|
102
|
+
}}
|
|
103
|
+
onmousedown={() => (activeRound = 'ceil')}
|
|
104
|
+
{disabled}
|
|
105
|
+
class={twMerge(
|
|
106
|
+
`slider-bg basis-[calc(${(centerNum / number.maxNum) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
|
|
107
|
+
accent-(--back-color)
|
|
108
|
+
[&::-webkit-slider-runnable-track]:rounded-l-full
|
|
109
|
+
[&::-webkit-slider-runnable-track]:bg-(--gray-color)
|
|
110
|
+
[&::-webkit-slider-runnable-track]:px-2
|
|
111
|
+
[&::-webkit-slider-thumb]:relative
|
|
112
|
+
[&::-webkit-slider-thumb]:z-100
|
|
113
|
+
[&::-webkit-slider-thumb]:size-4
|
|
114
|
+
[&::-webkit-slider-thumb]:cursor-pointer
|
|
115
|
+
[&::-webkit-slider-thumb]:rounded-full
|
|
116
|
+
[&::-webkit-slider-thumb]:shadow-red-500
|
|
117
|
+
${
|
|
118
|
+
userAgent.includes('iOS') || userAgent.includes('iPhone') || userAgent.includes('iPad')
|
|
119
|
+
? '[&::-webkit-slider-thumb]:ring-[6.5px]'
|
|
120
|
+
: '[&::-webkit-slider-thumb]:ring-[5px] '
|
|
121
|
+
}
|
|
122
|
+
[&::-moz-range-thumb]:relative
|
|
123
|
+
[&::-moz-range-thumb]:ml-[-0.4rem]
|
|
124
|
+
[&::-moz-range-thumb]:size-4
|
|
125
|
+
[&::-moz-range-thumb]:cursor-pointer
|
|
126
|
+
[&::-moz-range-thumb]:rounded-full
|
|
127
|
+
[&::-moz-range-thumb]:shadow-[var(--focus-shadow),]
|
|
128
|
+
[&::-moz-range-thumb]:ring-[6px]
|
|
129
|
+
[&::-moz-range-track]:rounded-full
|
|
130
|
+
[&::-moz-range-track]:bg-(--gray-color)
|
|
131
|
+
`,
|
|
132
|
+
`[&::-moz-range-thumb]:shadow-[calc(100rem+0.5rem)_0_0_100rem]
|
|
133
|
+
[&::-webkit-slider-thumb]:shadow-[calc(100rem+0.5rem)_0_0_100rem]`,
|
|
134
|
+
)}
|
|
135
|
+
/>
|
|
136
|
+
<input
|
|
137
|
+
type="range"
|
|
138
|
+
min={centerNum}
|
|
139
|
+
max={number.maxNum}
|
|
140
|
+
step={number.step}
|
|
141
|
+
bind:value={upperValue}
|
|
142
|
+
oninput={disabled
|
|
143
|
+
? undefined
|
|
144
|
+
: (e) => {
|
|
145
|
+
const newValue = Math.max(Number((e.target as HTMLInputElement).value), lowerValue)
|
|
146
|
+
upperValue = roundToClean(newValue == lowerValue ? newValue + number.step : upperValue)
|
|
147
|
+
}}
|
|
148
|
+
onmousedown={() => (activeRound = 'floor')}
|
|
149
|
+
{disabled}
|
|
150
|
+
class={twMerge(
|
|
151
|
+
`slider-bg basis-[calc(${100 - (centerNum / number.maxNum) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
|
|
152
|
+
accent-(--back-color)
|
|
153
|
+
[&::-webkit-slider-runnable-track]:rounded-r-full
|
|
154
|
+
[&::-webkit-slider-runnable-track]:bg-(--gray-color)
|
|
155
|
+
[&::-webkit-slider-runnable-track]:px-2
|
|
156
|
+
[&::-webkit-slider-thumb]:relative
|
|
157
|
+
[&::-webkit-slider-thumb]:z-100
|
|
158
|
+
[&::-webkit-slider-thumb]:size-4
|
|
159
|
+
[&::-webkit-slider-thumb]:cursor-pointer
|
|
160
|
+
[&::-webkit-slider-thumb]:rounded-full
|
|
161
|
+
[&::-webkit-slider-thumb]:shadow-red-500
|
|
162
|
+
${
|
|
163
|
+
userAgent.includes('iOS') || userAgent.includes('iPhone') || userAgent.includes('iPad')
|
|
164
|
+
? '[&::-webkit-slider-thumb]:ring-[6.5px]'
|
|
165
|
+
: '[&::-webkit-slider-thumb]:ring-[5px] '
|
|
166
|
+
}
|
|
167
|
+
[&::-moz-range-thumb]:relative
|
|
168
|
+
[&::-moz-range-thumb]:ml-[-0.4rem]
|
|
169
|
+
[&::-moz-range-thumb]:size-4
|
|
170
|
+
[&::-moz-range-thumb]:cursor-pointer
|
|
171
|
+
[&::-moz-range-thumb]:rounded-full
|
|
172
|
+
[&::-moz-range-thumb]:shadow-[var(--focus-shadow),]
|
|
173
|
+
[&::-moz-range-thumb]:ring-[6px]
|
|
174
|
+
[&::-moz-range-track]:rounded-full
|
|
175
|
+
[&::-moz-range-track]:bg-(--gray-color)
|
|
176
|
+
`,
|
|
177
|
+
`[&::-moz-range-thumb]:shadow-[calc(100rem*-1-0.5rem)_0_0_100rem]
|
|
178
|
+
[&::-webkit-slider-thumb]:shadow-[calc(100rem*-1-0.5rem)_0_0_100rem]`,
|
|
179
|
+
)}
|
|
180
|
+
/>
|
|
164
181
|
</div>
|
|
165
182
|
{:else}
|
|
183
|
+
{@const userAgent = navigator.userAgent}
|
|
166
184
|
<!-- Одиночный слайдер -->
|
|
167
|
-
<div
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
class="absolute z-10 h-full {singlePosition === 100 ? ' rounded-full' : 'rounded-l-full'}"
|
|
176
|
-
style={`width: ${singlePosition}%; background-color: var(--bg-color)`}
|
|
177
|
-
></div>
|
|
178
|
-
</div>
|
|
185
|
+
<div class="absolute h-full w-full">
|
|
186
|
+
<input
|
|
187
|
+
type="range"
|
|
188
|
+
class={twMerge(
|
|
189
|
+
`slider-bg h-8 w-full appearance-none overflow-hidden rounded-full accent-(--back-color)
|
|
190
|
+
[&::-webkit-slider-runnable-track]:rounded-full
|
|
191
|
+
[&::-webkit-slider-runnable-track]:bg-(--gray-color)
|
|
192
|
+
[&::-webkit-slider-thumb]:relative
|
|
179
193
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
[&::-webkit-slider-thumb]:ml-[-0.4rem]
|
|
195
|
+
[&::-webkit-slider-thumb]:h-4
|
|
196
|
+
[&::-webkit-slider-thumb]:w-4
|
|
197
|
+
[&::-webkit-slider-thumb]:cursor-pointer
|
|
198
|
+
[&::-webkit-slider-thumb]:rounded-full
|
|
199
|
+
[&::-webkit-slider-thumb]:shadow-[var(--focus-shadow),]
|
|
200
|
+
${
|
|
201
|
+
userAgent.includes('iOS') || userAgent.includes('iPhone') || userAgent.includes('iPad')
|
|
202
|
+
? 'pl-3.5 [&::-webkit-slider-thumb]:ring-[6.5px]'
|
|
203
|
+
: 'pl-3 [&::-webkit-slider-thumb]:ring-[5px]'
|
|
204
|
+
}
|
|
205
|
+
[&::-moz-range-thumb]:relative
|
|
206
|
+
[&::-moz-range-thumb]:ml-[-0.4rem]
|
|
207
|
+
[&::-moz-range-thumb]:size-4
|
|
208
|
+
[&::-moz-range-thumb]:cursor-pointer
|
|
209
|
+
[&::-moz-range-thumb]:rounded-full
|
|
210
|
+
[&::-moz-range-thumb]:shadow-[var(--focus-shadow),]
|
|
211
|
+
[&::-moz-range-thumb]:ring-[6px]
|
|
212
|
+
[&::-moz-range-track]:rounded-full
|
|
213
|
+
[&::-moz-range-track]:bg-(--gray-color)
|
|
214
|
+
`,
|
|
215
|
+
`[&::-moz-range-thumb]:shadow-[calc(100rem*-1-0.5rem)_0_0_100rem]
|
|
216
|
+
[&::-webkit-slider-thumb]:shadow-[calc(100rem*-1-0.5rem)_0_0_100rem]`,
|
|
217
|
+
)}
|
|
218
|
+
min={number.minNum}
|
|
219
|
+
max={number.maxNum}
|
|
220
|
+
step={number.step}
|
|
221
|
+
bind:value={singleValue}
|
|
222
|
+
/>
|
|
200
223
|
</div>
|
|
201
224
|
{/if}
|
|
202
225
|
</div>
|
|
203
226
|
|
|
204
227
|
<!-- Кнопки управления -->
|
|
205
|
-
<div class={`mt-
|
|
228
|
+
<div class={`mt-3 flex w-full ${isRange ? 'justify-between' : 'justify-center'} gap-2`}>
|
|
206
229
|
{#if isRange}
|
|
207
230
|
{#each ['lower', 'upper'] as type (type)}
|
|
208
231
|
<div
|
|
@@ -246,64 +269,3 @@
|
|
|
246
269
|
{/if}
|
|
247
270
|
</div>
|
|
248
271
|
</div>
|
|
249
|
-
|
|
250
|
-
<style>
|
|
251
|
-
input[type='range'] {
|
|
252
|
-
-webkit-appearance: none;
|
|
253
|
-
appearance: none;
|
|
254
|
-
margin: 0;
|
|
255
|
-
padding: 0;
|
|
256
|
-
pointer-events: none;
|
|
257
|
-
outline: none;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/* Webkit thumb */
|
|
261
|
-
input[type='range']::-webkit-slider-thumb {
|
|
262
|
-
-webkit-appearance: none;
|
|
263
|
-
appearance: none;
|
|
264
|
-
width: 2rem;
|
|
265
|
-
height: 2rem;
|
|
266
|
-
border-radius: 50%;
|
|
267
|
-
background: transparent;
|
|
268
|
-
cursor: pointer;
|
|
269
|
-
pointer-events: auto;
|
|
270
|
-
border: none;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/* Firefox thumb */
|
|
274
|
-
input[type='range']::-moz-range-thumb {
|
|
275
|
-
width: 2rem;
|
|
276
|
-
height: 2rem;
|
|
277
|
-
border-radius: 50%;
|
|
278
|
-
background: transparent;
|
|
279
|
-
cursor: pointer;
|
|
280
|
-
pointer-events: auto;
|
|
281
|
-
border: none;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/* Webkit track */
|
|
285
|
-
input[type='range']::-webkit-slider-runnable-track {
|
|
286
|
-
width: 100%;
|
|
287
|
-
height: 100%;
|
|
288
|
-
background: transparent;
|
|
289
|
-
border-radius: 0;
|
|
290
|
-
border: none;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/* Firefox track */
|
|
294
|
-
input[type='range']::-moz-range-track {
|
|
295
|
-
width: 100%;
|
|
296
|
-
height: 100%;
|
|
297
|
-
background: transparent;
|
|
298
|
-
border-radius: 0;
|
|
299
|
-
border: none;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
input[type='range']:disabled::-webkit-slider-thumb {
|
|
303
|
-
cursor: not-allowed;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
input[type='range']:disabled::-moz-range-thumb {
|
|
307
|
-
cursor: not-allowed;
|
|
308
|
-
}
|
|
309
|
-
</style>
|