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.
@@ -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
- <div class="flex w-full gap-4">
135
- <UI.Input
136
- label={{ name: $t('constructor.props.range.start') }}
137
- value={component.properties.range.start}
138
- onUpdate={(value) => {
139
- updateProperty('range.start', value as number, component, onPropertyChange)
140
- generateBitOptions(component.properties.range.start, component.properties.range.end)
141
- }}
142
- number={{ minNum: 0, maxNum: 31, step: 1 }}
143
- help={{ info: $t('constructor.props.range.start.help') }}
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
- number={{ minNum: 0, maxNum: 31, step: 1 }}
154
- help={{ info: $t('constructor.props.range.end.help') }}
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
- const singlePosition = $derived(((singleValue - number.minNum) / (number.maxNum - number.minNum)) * 100)
32
- const lowerPosition = $derived(((lowerValue - number.minNum) / (number.maxNum - number.minNum)) * 100)
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
- let activeThumb = $state<'lower' | 'upper'>('lower')
67
- const handleTrackClick = (e: MouseEvent) => {
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
- if (isRange) {
76
- const lowerDiff = Math.abs(clickValue - lowerValue)
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
- activeThumb = lowerDiff < upperDiff ? 'lower' : 'upper'
71
+ const rounded2 = Number(num.toFixed(2))
72
+ if (Math.abs(rounded2 - num) < 1e-10) return rounded2
80
73
 
81
- if (activeThumb === 'lower') {
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-7 w-full justify-center rounded-full {disabled ? 'cursor-not-allowed opacity-50' : ''}"
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
- class={`absolute h-full w-full rounded-full bg-(--gray-color) ${disabled ? '' : 'cursor-pointer'}`}
109
- role="button"
110
- tabindex={null}
111
- onkeydown={null}
112
- onclick={disabled ? undefined : handleTrackClick}
113
- >
114
- <div class="absolute h-full rounded-full bg-(--bg-color)" style={`left: ${lowerPosition}%; right: ${100 - upperPosition}%;`}></div>
115
- </div>
116
-
117
- <!-- Ползунки -->
118
- <input
119
- type="range"
120
- min={number.minNum}
121
- max={number.maxNum}
122
- step={number.step}
123
- bind:value={lowerValue}
124
- oninput={disabled
125
- ? undefined
126
- : (e) => {
127
- const newValue = Math.min(Number((e.target as HTMLInputElement).value), upperValue)
128
- lowerValue = newValue
129
- activeThumb = 'lower'
130
- }}
131
- onmouseup={disabled ? undefined : () => onUpdate([lowerValue, upperValue])}
132
- {disabled}
133
- class={`absolute h-full w-full appearance-none bg-transparent ${activeThumb === 'lower' ? 'z-30' : 'z-20'}`}
134
- />
135
- <div
136
- class="pointer-events-none absolute z-40 rounded-full bg-(--field-color)"
137
- style={`left: calc(${lowerPosition}% + 0rem); top: 50%; transform: translateY(-50%)`}
138
- >
139
- <IconGripVerticalLeft />
140
- </div>
141
-
142
- <input
143
- type="range"
144
- min={number.minNum}
145
- max={number.maxNum}
146
- step={number.step}
147
- bind:value={upperValue}
148
- oninput={disabled
149
- ? undefined
150
- : (e) => {
151
- const newValue = Math.max(Number((e.target as HTMLInputElement).value), lowerValue)
152
- upperValue = newValue
153
- activeThumb = 'upper'
154
- }}
155
- onmouseup={disabled ? undefined : () => onUpdate([lowerValue, upperValue])}
156
- {disabled}
157
- class={`absolute h-full w-full appearance-none bg-transparent ${activeThumb === 'upper' ? 'z-30' : 'z-20'}`}
158
- />
159
- <div
160
- class="pointer-events-none absolute z-40 rounded-full bg-(--field-color)"
161
- style={`left: calc(${upperPosition}% - 2rem); top: 50%; transform: translateY(-50%)`}
162
- >
163
- <IconGripVerticalRight />
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
- class={`absolute h-full w-full rounded-full bg-(--gray-color) ${disabled ? '' : 'cursor-pointer'}`}
169
- role="button"
170
- tabindex={null}
171
- onkeydown={null}
172
- onclick={disabled ? undefined : handleTrackClick}
173
- >
174
- <div
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
- <input
181
- type="range"
182
- min={number.minNum}
183
- max={number.maxNum}
184
- step={number.step}
185
- bind:value={singleValue}
186
- oninput={disabled
187
- ? undefined
188
- : (e) => {
189
- singleValue = Number((e.target as HTMLInputElement).value)
190
- }}
191
- onmouseup={disabled ? undefined : () => onUpdate(singleValue)}
192
- {disabled}
193
- class="absolute z-20 h-full w-full appearance-none"
194
- />
195
- <div
196
- class="pointer-events-none absolute z-30 rounded-full bg-(--field-color)"
197
- style={`left: clamp(1rem, ${singlePosition}%, calc(100% - 1rem)); top: 50%; transform: translate(-50%, -50%)`}
198
- >
199
- <IconGripVerticalDual />
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-2 flex w-full ${isRange ? 'justify-between' : 'justify-center'} gap-2`}>
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>