poe-svelte-ui-lib 1.0.0 → 1.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.
- package/LICENSE +3 -3
- package/dist/Accordion/Accordion.svelte +53 -53
- package/dist/Accordion/AccordionProps.svelte +70 -70
- package/dist/Button/Button.svelte +144 -144
- package/dist/Button/ButtonProps.svelte +200 -200
- package/dist/ColorPicker/ColorPicker.svelte +207 -207
- package/dist/ColorPicker/ColorPickerProps.svelte +100 -100
- package/dist/FileAttach/FileAttach.svelte +103 -103
- package/dist/Graph/Graph.svelte +270 -270
- package/dist/Graph/GraphProps.svelte +56 -56
- package/dist/Input/Input.svelte +239 -239
- package/dist/Input/InputProps.svelte +221 -221
- package/dist/Loader.svelte +12 -12
- package/dist/MessageModal.svelte +54 -54
- package/dist/ProgressBar/ProgressBar.svelte +48 -48
- package/dist/ProgressBar/ProgressBarProps.svelte +145 -145
- package/dist/Select/Select.svelte +191 -187
- package/dist/Select/SelectProps.svelte +260 -260
- package/dist/Slider/Slider.svelte +260 -260
- package/dist/Slider/SliderProps.svelte +161 -161
- package/dist/Switch/Switch.svelte +83 -83
- package/dist/Switch/SwitchProps.svelte +144 -144
- package/dist/Table/Table.svelte +276 -276
- package/dist/Table/TableProps.svelte +286 -286
- package/dist/TextField/TextField.svelte +22 -22
- package/dist/TextField/TextFieldProps.svelte +92 -92
- package/dist/{appIcons → libIcons}/ButtonAdd.svelte +10 -10
- package/dist/{appIcons → libIcons}/ButtonDelete.svelte +13 -13
- package/dist/{appIcons → libIcons}/LoaderRotate.svelte +9 -9
- package/dist/locales/CircleFlagsEn.svelte +14 -14
- package/dist/locales/CircleFlagsRu.svelte +8 -8
- package/dist/locales/CircleFlagsZh.svelte +8 -8
- package/package.json +49 -47
- /package/dist/{appIcons → libIcons}/ButtonAdd.svelte.d.ts +0 -0
- /package/dist/{appIcons → libIcons}/ButtonDelete.svelte.d.ts +0 -0
- /package/dist/{appIcons → libIcons}/LoaderRotate.svelte.d.ts +0 -0
package/LICENSE
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Looks like there's already a license file for this project.
|
|
2
|
-
[?25l[2K[1G[36m?[39m [1mDo you want to replace it?[22m [90m┬╗[39m [90m(y/N)[39m[2K[1G[2K[1G[32mтИЪ[39m [1mDo you want to replace it?[22m [90m...[39m no
|
|
3
|
-
[?25hExiting...
|
|
1
|
+
Looks like there's already a license file for this project.
|
|
2
|
+
[?25l[2K[1G[36m?[39m [1mDo you want to replace it?[22m [90m┬╗[39m [90m(y/N)[39m[2K[1G[2K[1G[32mтИЪ[39m [1mDo you want to replace it?[22m [90m...[39m no
|
|
3
|
+
[?25hExiting...
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { slide } from 'svelte/transition'
|
|
3
|
-
import type { IAccordionProps } from '../types'
|
|
4
|
-
|
|
5
|
-
const props: IAccordionProps = $props()
|
|
6
|
-
let isOpen = $state(props.isOpen)
|
|
7
|
-
|
|
8
|
-
const toggle = () => (isOpen = !isOpen)
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<div
|
|
12
|
-
id={props.id?.value}
|
|
13
|
-
class="border-[var(--border-color)] p-0 transition-shadow duration-250 {props.type === 'sub'
|
|
14
|
-
? 'border-none'
|
|
15
|
-
: 'rounded-xl border bg-[var(--conteiner-color)] hover:shadow-md'}
|
|
16
|
-
{props.componentClass}"
|
|
17
|
-
transition:slide={{ duration: 250 }}
|
|
18
|
-
>
|
|
19
|
-
<button
|
|
20
|
-
class="flex w-full cursor-pointer items-center justify-between transition-shadow duration-250 sm:p-4 sm:px-6
|
|
21
|
-
{props.type === 'sub' ? 'border-b border-[var(--border-color)]' : ''}"
|
|
22
|
-
onclick={toggle}
|
|
23
|
-
>
|
|
24
|
-
<span class="toggle m-0 cursor-pointer text-lg font-semibold {props.label?.class}">
|
|
25
|
-
{props.label?.name}
|
|
26
|
-
</span>
|
|
27
|
-
<svg
|
|
28
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
29
|
-
class="arrow h-[1.1rem] w-[1.1rem] transition-transform duration-250"
|
|
30
|
-
style="transform: rotate({isOpen ? 180 : 0}deg)"
|
|
31
|
-
viewBox="0 0 24 24"
|
|
32
|
-
>
|
|
33
|
-
<path
|
|
34
|
-
fill="none"
|
|
35
|
-
stroke="currentColor"
|
|
36
|
-
stroke-linecap="round"
|
|
37
|
-
stroke-linejoin="round"
|
|
38
|
-
stroke-width="1.5"
|
|
39
|
-
d="M18 12.5s-4.419 6-6 6s-6-6-6-6m12-7s-4.419 6-6 6s-6-6-6-6"
|
|
40
|
-
color="currentColor"
|
|
41
|
-
/>
|
|
42
|
-
</svg>
|
|
43
|
-
</button>
|
|
44
|
-
|
|
45
|
-
{#if isOpen}
|
|
46
|
-
<div
|
|
47
|
-
class="flex w-full flex-wrap items-start justify-around p-4 sm:p-3 {props.type === 'sub' ? '' : 'border-t border-[var(--border-color)]'}"
|
|
48
|
-
transition:slide={{ duration: 250 }}
|
|
49
|
-
>
|
|
50
|
-
{@render props.children?.()}
|
|
51
|
-
</div>
|
|
52
|
-
{/if}
|
|
53
|
-
</div>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { slide } from 'svelte/transition'
|
|
3
|
+
import type { IAccordionProps } from '../types'
|
|
4
|
+
|
|
5
|
+
const props: IAccordionProps = $props()
|
|
6
|
+
let isOpen = $state(props.isOpen)
|
|
7
|
+
|
|
8
|
+
const toggle = () => (isOpen = !isOpen)
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<div
|
|
12
|
+
id={props.id?.value}
|
|
13
|
+
class="border-[var(--border-color)] p-0 transition-shadow duration-250 {props.type === 'sub'
|
|
14
|
+
? 'border-none'
|
|
15
|
+
: 'rounded-xl border bg-[var(--conteiner-color)] hover:shadow-md'}
|
|
16
|
+
{props.componentClass}"
|
|
17
|
+
transition:slide={{ duration: 250 }}
|
|
18
|
+
>
|
|
19
|
+
<button
|
|
20
|
+
class="flex w-full cursor-pointer items-center justify-between transition-shadow duration-250 sm:p-4 sm:px-6
|
|
21
|
+
{props.type === 'sub' ? 'border-b border-[var(--border-color)]' : ''}"
|
|
22
|
+
onclick={toggle}
|
|
23
|
+
>
|
|
24
|
+
<span class="toggle m-0 cursor-pointer text-lg font-semibold {props.label?.class}">
|
|
25
|
+
{props.label?.name}
|
|
26
|
+
</span>
|
|
27
|
+
<svg
|
|
28
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
29
|
+
class="arrow h-[1.1rem] w-[1.1rem] transition-transform duration-250"
|
|
30
|
+
style="transform: rotate({isOpen ? 180 : 0}deg)"
|
|
31
|
+
viewBox="0 0 24 24"
|
|
32
|
+
>
|
|
33
|
+
<path
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke="currentColor"
|
|
36
|
+
stroke-linecap="round"
|
|
37
|
+
stroke-linejoin="round"
|
|
38
|
+
stroke-width="1.5"
|
|
39
|
+
d="M18 12.5s-4.419 6-6 6s-6-6-6-6m12-7s-4.419 6-6 6s-6-6-6-6"
|
|
40
|
+
color="currentColor"
|
|
41
|
+
/>
|
|
42
|
+
</svg>
|
|
43
|
+
</button>
|
|
44
|
+
|
|
45
|
+
{#if isOpen}
|
|
46
|
+
<div
|
|
47
|
+
class="flex w-full flex-wrap items-start justify-around p-4 sm:p-3 {props.type === 'sub' ? '' : 'border-t border-[var(--border-color)]'}"
|
|
48
|
+
transition:slide={{ duration: 250 }}
|
|
49
|
+
>
|
|
50
|
+
{@render props.children?.()}
|
|
51
|
+
</div>
|
|
52
|
+
{/if}
|
|
53
|
+
</div>
|
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
<!-- $lib/ElementsUI/AccordionProps.svelte -->
|
|
2
|
-
<script lang="ts">
|
|
3
|
-
import { t } from '../locales/i18n'
|
|
4
|
-
import type { IAccordionProps, UIComponent } from '../types'
|
|
5
|
-
import * as UI from '../index'
|
|
6
|
-
import { optionsStore } from '../options'
|
|
7
|
-
|
|
8
|
-
const { component, onPropertyChange } = $props<{
|
|
9
|
-
component: UIComponent & { properties: Partial<IAccordionProps> }
|
|
10
|
-
onPropertyChange: (value: string | object) => void
|
|
11
|
-
}>()
|
|
12
|
-
|
|
13
|
-
let currentType = $derived($optionsStore.ACCORDION_TYPE_OPTIONS.find((t) => t.value === component.properties.type))
|
|
14
|
-
|
|
15
|
-
/* Обновление свойства */
|
|
16
|
-
const updateProperty = (path: string, value: string | object) => {
|
|
17
|
-
const newProperties = JSON.parse(JSON.stringify(component.properties))
|
|
18
|
-
const parts = path.split('.')
|
|
19
|
-
let obj = newProperties
|
|
20
|
-
|
|
21
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
22
|
-
const part = parts[i]
|
|
23
|
-
if (!obj[part]) obj[part] = {}
|
|
24
|
-
obj = obj[part]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
obj[parts[parts.length - 1]] = value
|
|
28
|
-
onPropertyChange(newProperties)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const handleImageUpload = (event: Event) => {
|
|
32
|
-
const input = event.target as HTMLInputElement
|
|
33
|
-
if (!input.files || input.files.length === 0) return
|
|
34
|
-
|
|
35
|
-
const file = input.files[0]
|
|
36
|
-
const reader = new FileReader()
|
|
37
|
-
reader.onload = (e) => {
|
|
38
|
-
const base64String = e.target?.result as string
|
|
39
|
-
updateProperty('image', base64String)
|
|
40
|
-
}
|
|
41
|
-
reader.readAsDataURL(file)
|
|
42
|
-
}
|
|
43
|
-
</script>
|
|
44
|
-
|
|
45
|
-
{#if component && component.properties}
|
|
46
|
-
<div class="flex items-center justify-center gap-8">
|
|
47
|
-
<UI.Input
|
|
48
|
-
label={{ name: $t('service.constructor.props.label') }}
|
|
49
|
-
wrapperClass="!w-1/3"
|
|
50
|
-
value={component.properties.label.name}
|
|
51
|
-
onUpdate={(value) => updateProperty('label.name', value as string)}
|
|
52
|
-
type="text"
|
|
53
|
-
/>
|
|
54
|
-
<UI.Select
|
|
55
|
-
wrapperClass="!w-1/3"
|
|
56
|
-
label={{ name: $t('service.constructor.props.type') }}
|
|
57
|
-
type="buttons"
|
|
58
|
-
value={currentType}
|
|
59
|
-
options={$optionsStore.ACCORDION_TYPE_OPTIONS}
|
|
60
|
-
onUpdate={(item) => updateProperty('type', item.value as string)}
|
|
61
|
-
/>
|
|
62
|
-
<UI.FileAttach
|
|
63
|
-
type="image"
|
|
64
|
-
label={{ name: $t('service.constructor.props.image') }}
|
|
65
|
-
accept="image/png, image/jpeg, image/webp"
|
|
66
|
-
onChange={handleImageUpload}
|
|
67
|
-
/>
|
|
68
|
-
<UI.Button name={$t('service.constructor.props.removeimage')} wrapperClass="!w-auto" componentClass="px-4" onClick={() => updateProperty('image', '')} />
|
|
69
|
-
</div>
|
|
70
|
-
{/if}
|
|
1
|
+
<!-- $lib/ElementsUI/AccordionProps.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import { t } from '../locales/i18n'
|
|
4
|
+
import type { IAccordionProps, UIComponent } from '../types'
|
|
5
|
+
import * as UI from '../index'
|
|
6
|
+
import { optionsStore } from '../options'
|
|
7
|
+
|
|
8
|
+
const { component, onPropertyChange } = $props<{
|
|
9
|
+
component: UIComponent & { properties: Partial<IAccordionProps> }
|
|
10
|
+
onPropertyChange: (value: string | object) => void
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
let currentType = $derived($optionsStore.ACCORDION_TYPE_OPTIONS.find((t) => t.value === component.properties.type))
|
|
14
|
+
|
|
15
|
+
/* Обновление свойства */
|
|
16
|
+
const updateProperty = (path: string, value: string | object) => {
|
|
17
|
+
const newProperties = JSON.parse(JSON.stringify(component.properties))
|
|
18
|
+
const parts = path.split('.')
|
|
19
|
+
let obj = newProperties
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
22
|
+
const part = parts[i]
|
|
23
|
+
if (!obj[part]) obj[part] = {}
|
|
24
|
+
obj = obj[part]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
obj[parts[parts.length - 1]] = value
|
|
28
|
+
onPropertyChange(newProperties)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const handleImageUpload = (event: Event) => {
|
|
32
|
+
const input = event.target as HTMLInputElement
|
|
33
|
+
if (!input.files || input.files.length === 0) return
|
|
34
|
+
|
|
35
|
+
const file = input.files[0]
|
|
36
|
+
const reader = new FileReader()
|
|
37
|
+
reader.onload = (e) => {
|
|
38
|
+
const base64String = e.target?.result as string
|
|
39
|
+
updateProperty('image', base64String)
|
|
40
|
+
}
|
|
41
|
+
reader.readAsDataURL(file)
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
{#if component && component.properties}
|
|
46
|
+
<div class="flex items-center justify-center gap-8">
|
|
47
|
+
<UI.Input
|
|
48
|
+
label={{ name: $t('service.constructor.props.label') }}
|
|
49
|
+
wrapperClass="!w-1/3"
|
|
50
|
+
value={component.properties.label.name}
|
|
51
|
+
onUpdate={(value) => updateProperty('label.name', value as string)}
|
|
52
|
+
type="text"
|
|
53
|
+
/>
|
|
54
|
+
<UI.Select
|
|
55
|
+
wrapperClass="!w-1/3"
|
|
56
|
+
label={{ name: $t('service.constructor.props.type') }}
|
|
57
|
+
type="buttons"
|
|
58
|
+
value={currentType}
|
|
59
|
+
options={$optionsStore.ACCORDION_TYPE_OPTIONS}
|
|
60
|
+
onUpdate={(item) => updateProperty('type', item.value as string)}
|
|
61
|
+
/>
|
|
62
|
+
<UI.FileAttach
|
|
63
|
+
type="image"
|
|
64
|
+
label={{ name: $t('service.constructor.props.image') }}
|
|
65
|
+
accept="image/png, image/jpeg, image/webp"
|
|
66
|
+
onChange={handleImageUpload}
|
|
67
|
+
/>
|
|
68
|
+
<UI.Button name={$t('service.constructor.props.removeimage')} wrapperClass="!w-auto" componentClass="px-4" onClick={() => updateProperty('image', '')} />
|
|
69
|
+
</div>
|
|
70
|
+
{/if}
|
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
<!-- $lib/ElementsUI/Button.svelte -->
|
|
2
|
-
<script lang="ts">
|
|
3
|
-
import { onMount } from 'svelte'
|
|
4
|
-
// import { type IButtonProps } from '../types'
|
|
5
|
-
import { fly } from 'svelte/transition'
|
|
6
|
-
|
|
7
|
-
interface IUIComponentHandler {
|
|
8
|
-
Header?: string
|
|
9
|
-
Argument?: string
|
|
10
|
-
Value?: string
|
|
11
|
-
Variables?: string[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface IButtonProps {
|
|
15
|
-
id?: { value?: string; name?: string }
|
|
16
|
-
wrapperClass?: string
|
|
17
|
-
label?: { name?: string; class?: string }
|
|
18
|
-
componentClass?: string
|
|
19
|
-
name?: string
|
|
20
|
-
icon?: {
|
|
21
|
-
component?: ConstructorOfATypedSvelteComponent | null
|
|
22
|
-
properties?: Record<string, unknown>
|
|
23
|
-
}
|
|
24
|
-
info?: string
|
|
25
|
-
keyBind?: {
|
|
26
|
-
key?: string
|
|
27
|
-
ctrlKey?: boolean
|
|
28
|
-
shiftKey?: boolean
|
|
29
|
-
altKey?: boolean
|
|
30
|
-
metaKey?: boolean /* Поддержка Meta (Cmd на Mac) */
|
|
31
|
-
}
|
|
32
|
-
disabled?: boolean
|
|
33
|
-
eventHandler?: IUIComponentHandler
|
|
34
|
-
onClick?: () => void
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
let {
|
|
38
|
-
id = { value: crypto.randomUUID(), name: '' },
|
|
39
|
-
wrapperClass = 'bg-blue',
|
|
40
|
-
label = { name: '', class: '' },
|
|
41
|
-
name = '',
|
|
42
|
-
componentClass = '',
|
|
43
|
-
icon = { component: null, properties: {} },
|
|
44
|
-
info = '',
|
|
45
|
-
disabled = false,
|
|
46
|
-
keyBind,
|
|
47
|
-
onClick,
|
|
48
|
-
}: IButtonProps = $props()
|
|
49
|
-
|
|
50
|
-
let showInfo = $state(false)
|
|
51
|
-
|
|
52
|
-
/* Обработчик клика */
|
|
53
|
-
const handleClick = () => {
|
|
54
|
-
if (disabled || !onClick) return
|
|
55
|
-
onClick()
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/* Обработчик горячих клавиш */
|
|
59
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
60
|
-
if (disabled || !keyBind || !onClick) return
|
|
61
|
-
|
|
62
|
-
const isKeyMatch = event.key === keyBind.key
|
|
63
|
-
const isCtrlMatch = keyBind.ctrlKey === undefined || event.ctrlKey === keyBind.ctrlKey
|
|
64
|
-
const isShiftMatch = keyBind.shiftKey === undefined || event.shiftKey === keyBind.shiftKey
|
|
65
|
-
const isAltMatch = keyBind.altKey === undefined || event.altKey === keyBind.altKey
|
|
66
|
-
const isMetaMatch = keyBind.metaKey === undefined || event.metaKey === keyBind.metaKey
|
|
67
|
-
|
|
68
|
-
if (isKeyMatch && isCtrlMatch && isShiftMatch && isAltMatch && isMetaMatch) {
|
|
69
|
-
event.preventDefault()
|
|
70
|
-
onClick()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* Подписка на события клавиатуры */
|
|
75
|
-
onMount(() => {
|
|
76
|
-
if (keyBind) {
|
|
77
|
-
window.addEventListener('keydown', handleKeyDown)
|
|
78
|
-
}
|
|
79
|
-
return () => {
|
|
80
|
-
if (keyBind) {
|
|
81
|
-
window.removeEventListener('keydown', handleKeyDown)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
</script>
|
|
86
|
-
|
|
87
|
-
<div class={`relative flex w-full flex-col items-center ${wrapperClass}`}>
|
|
88
|
-
{#if label.name}
|
|
89
|
-
<h5 class={`w-full px-4 text-center ${label.class}`}>{label.name}</h5>
|
|
90
|
-
{/if}
|
|
91
|
-
|
|
92
|
-
<div class="relative flex w-full grow items-center">
|
|
93
|
-
<button
|
|
94
|
-
id={id.value}
|
|
95
|
-
class={`
|
|
96
|
-
relative m-0 inline-block w-full items-center rounded-2xl border border-[var(--bg-color)] bg-[var(--bg-color)]
|
|
97
|
-
px-2 py-1 font-semibold transition duration-200 select-none
|
|
98
|
-
${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:shadow-md active:scale-97'}
|
|
99
|
-
${componentClass}
|
|
100
|
-
`}
|
|
101
|
-
onclick={handleClick}
|
|
102
|
-
{disabled}
|
|
103
|
-
aria-label={name || label.name}
|
|
104
|
-
onmouseenter={() => {
|
|
105
|
-
if (info) showInfo = true
|
|
106
|
-
}}
|
|
107
|
-
onmouseleave={() => {
|
|
108
|
-
if (info) showInfo = false
|
|
109
|
-
}}
|
|
110
|
-
>
|
|
111
|
-
<span class="flex flex-row items-center justify-center gap-2">
|
|
112
|
-
{#if icon?.component}
|
|
113
|
-
{@const IconComponent = icon?.component}
|
|
114
|
-
<IconComponent {...icon?.properties} />
|
|
115
|
-
{/if}
|
|
116
|
-
{#if name}
|
|
117
|
-
<div class="flex-1">
|
|
118
|
-
{name}
|
|
119
|
-
{#if keyBind}
|
|
120
|
-
<div class="text-xs opacity-70">
|
|
121
|
-
({keyBind.ctrlKey ? 'Ctrl+' : ''}{keyBind.shiftKey ? 'Shift+' : ''}{keyBind.altKey ? 'Alt+' : ''}{keyBind.key})
|
|
122
|
-
</div>
|
|
123
|
-
{/if}
|
|
124
|
-
</div>
|
|
125
|
-
{/if}
|
|
126
|
-
</span>
|
|
127
|
-
</button>
|
|
128
|
-
|
|
129
|
-
{#if showInfo}
|
|
130
|
-
<div
|
|
131
|
-
transition:fly={{ y: -15, duration: 300 }}
|
|
132
|
-
class="absolute bottom-full left-1/2 z-50 mb-2 w-max max-w-xs rounded-md px-3 py-1 text-sm shadow-lg"
|
|
133
|
-
style="background: color-mix(in srgb, var(--yellow-color) 30%, var(--back-color)); transform: translateX(-50%);"
|
|
134
|
-
>
|
|
135
|
-
{info}
|
|
136
|
-
<!-- Треугольная стрелка -->
|
|
137
|
-
<div
|
|
138
|
-
class="absolute top-full left-1/2 h-2 w-2 -translate-x-1/2 -translate-y-1/2 rotate-45 transform"
|
|
139
|
-
style="background: color-mix(in srgb, var(--yellow-color) 30%, var(--back-color));"
|
|
140
|
-
></div>
|
|
141
|
-
</div>
|
|
142
|
-
{/if}
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
1
|
+
<!-- $lib/ElementsUI/Button.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import { onMount } from 'svelte'
|
|
4
|
+
// import { type IButtonProps } from '../types'
|
|
5
|
+
import { fly } from 'svelte/transition'
|
|
6
|
+
|
|
7
|
+
interface IUIComponentHandler {
|
|
8
|
+
Header?: string
|
|
9
|
+
Argument?: string
|
|
10
|
+
Value?: string
|
|
11
|
+
Variables?: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface IButtonProps {
|
|
15
|
+
id?: { value?: string; name?: string }
|
|
16
|
+
wrapperClass?: string
|
|
17
|
+
label?: { name?: string; class?: string }
|
|
18
|
+
componentClass?: string
|
|
19
|
+
name?: string
|
|
20
|
+
icon?: {
|
|
21
|
+
component?: ConstructorOfATypedSvelteComponent | null
|
|
22
|
+
properties?: Record<string, unknown>
|
|
23
|
+
}
|
|
24
|
+
info?: string
|
|
25
|
+
keyBind?: {
|
|
26
|
+
key?: string
|
|
27
|
+
ctrlKey?: boolean
|
|
28
|
+
shiftKey?: boolean
|
|
29
|
+
altKey?: boolean
|
|
30
|
+
metaKey?: boolean /* Поддержка Meta (Cmd на Mac) */
|
|
31
|
+
}
|
|
32
|
+
disabled?: boolean
|
|
33
|
+
eventHandler?: IUIComponentHandler
|
|
34
|
+
onClick?: () => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let {
|
|
38
|
+
id = { value: crypto.randomUUID(), name: '' },
|
|
39
|
+
wrapperClass = 'bg-blue',
|
|
40
|
+
label = { name: '', class: '' },
|
|
41
|
+
name = '',
|
|
42
|
+
componentClass = '',
|
|
43
|
+
icon = { component: null, properties: {} },
|
|
44
|
+
info = '',
|
|
45
|
+
disabled = false,
|
|
46
|
+
keyBind,
|
|
47
|
+
onClick,
|
|
48
|
+
}: IButtonProps = $props()
|
|
49
|
+
|
|
50
|
+
let showInfo = $state(false)
|
|
51
|
+
|
|
52
|
+
/* Обработчик клика */
|
|
53
|
+
const handleClick = () => {
|
|
54
|
+
if (disabled || !onClick) return
|
|
55
|
+
onClick()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Обработчик горячих клавиш */
|
|
59
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
60
|
+
if (disabled || !keyBind || !onClick) return
|
|
61
|
+
|
|
62
|
+
const isKeyMatch = event.key === keyBind.key
|
|
63
|
+
const isCtrlMatch = keyBind.ctrlKey === undefined || event.ctrlKey === keyBind.ctrlKey
|
|
64
|
+
const isShiftMatch = keyBind.shiftKey === undefined || event.shiftKey === keyBind.shiftKey
|
|
65
|
+
const isAltMatch = keyBind.altKey === undefined || event.altKey === keyBind.altKey
|
|
66
|
+
const isMetaMatch = keyBind.metaKey === undefined || event.metaKey === keyBind.metaKey
|
|
67
|
+
|
|
68
|
+
if (isKeyMatch && isCtrlMatch && isShiftMatch && isAltMatch && isMetaMatch) {
|
|
69
|
+
event.preventDefault()
|
|
70
|
+
onClick()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Подписка на события клавиатуры */
|
|
75
|
+
onMount(() => {
|
|
76
|
+
if (keyBind) {
|
|
77
|
+
window.addEventListener('keydown', handleKeyDown)
|
|
78
|
+
}
|
|
79
|
+
return () => {
|
|
80
|
+
if (keyBind) {
|
|
81
|
+
window.removeEventListener('keydown', handleKeyDown)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<div class={`relative flex w-full flex-col items-center ${wrapperClass}`}>
|
|
88
|
+
{#if label.name}
|
|
89
|
+
<h5 class={`w-full px-4 text-center ${label.class}`}>{label.name}</h5>
|
|
90
|
+
{/if}
|
|
91
|
+
|
|
92
|
+
<div class="relative flex w-full grow items-center">
|
|
93
|
+
<button
|
|
94
|
+
id={id.value}
|
|
95
|
+
class={`
|
|
96
|
+
relative m-0 inline-block w-full items-center rounded-2xl border border-[var(--bg-color)] bg-[var(--bg-color)]
|
|
97
|
+
px-2 py-1 font-semibold transition duration-200 select-none
|
|
98
|
+
${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:shadow-md active:scale-97'}
|
|
99
|
+
${componentClass}
|
|
100
|
+
`}
|
|
101
|
+
onclick={handleClick}
|
|
102
|
+
{disabled}
|
|
103
|
+
aria-label={name || label.name}
|
|
104
|
+
onmouseenter={() => {
|
|
105
|
+
if (info) showInfo = true
|
|
106
|
+
}}
|
|
107
|
+
onmouseleave={() => {
|
|
108
|
+
if (info) showInfo = false
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<span class="flex flex-row items-center justify-center gap-2">
|
|
112
|
+
{#if icon?.component}
|
|
113
|
+
{@const IconComponent = icon?.component}
|
|
114
|
+
<IconComponent {...icon?.properties} />
|
|
115
|
+
{/if}
|
|
116
|
+
{#if name}
|
|
117
|
+
<div class="flex-1">
|
|
118
|
+
{name}
|
|
119
|
+
{#if keyBind}
|
|
120
|
+
<div class="text-xs opacity-70">
|
|
121
|
+
({keyBind.ctrlKey ? 'Ctrl+' : ''}{keyBind.shiftKey ? 'Shift+' : ''}{keyBind.altKey ? 'Alt+' : ''}{keyBind.key})
|
|
122
|
+
</div>
|
|
123
|
+
{/if}
|
|
124
|
+
</div>
|
|
125
|
+
{/if}
|
|
126
|
+
</span>
|
|
127
|
+
</button>
|
|
128
|
+
|
|
129
|
+
{#if showInfo}
|
|
130
|
+
<div
|
|
131
|
+
transition:fly={{ y: -15, duration: 300 }}
|
|
132
|
+
class="absolute bottom-full left-1/2 z-50 mb-2 w-max max-w-xs rounded-md px-3 py-1 text-sm shadow-lg"
|
|
133
|
+
style="background: color-mix(in srgb, var(--yellow-color) 30%, var(--back-color)); transform: translateX(-50%);"
|
|
134
|
+
>
|
|
135
|
+
{info}
|
|
136
|
+
<!-- Треугольная стрелка -->
|
|
137
|
+
<div
|
|
138
|
+
class="absolute top-full left-1/2 h-2 w-2 -translate-x-1/2 -translate-y-1/2 rotate-45 transform"
|
|
139
|
+
style="background: color-mix(in srgb, var(--yellow-color) 30%, var(--back-color));"
|
|
140
|
+
></div>
|
|
141
|
+
</div>
|
|
142
|
+
{/if}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|