@stacksjs/components 0.2.92 → 0.2.94
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/{Sidebar-yb115a2w.stx → Sidebar-4hn7vw4n.stx} +18 -0
- package/dist/index.js +5 -5
- package/package.json +5 -6
- package/src/components/CodeBlock.stx +0 -54
- package/src/components/Footer.stx +0 -66
- package/src/components/Hero.stx +0 -25
- package/src/components/Installation.stx +0 -37
- package/src/components/index.ts +0 -4
- package/src/composables/index.ts +0 -4
- package/src/composables/useCopyCode.ts +0 -73
- package/src/composables/useDarkMode.ts +0 -100
- package/src/composables/useSEO.ts +0 -230
- package/src/composables/utilities/index.ts +0 -7
- package/src/composables/utilities/useClickOutside.ts +0 -72
- package/src/composables/utilities/useFocusTrap.ts +0 -159
- package/src/composables/utilities/useKeyboard.ts +0 -124
- package/src/composables/utilities/useLocalStorage.ts +0 -132
- package/src/composables/utilities/useMediaQuery.ts +0 -118
- package/src/index.ts +0 -99
- package/src/stx.d.ts +0 -4
- package/src/ui/accordion/Accordion.stx +0 -146
- package/src/ui/accordion/AccordionItem.stx +0 -37
- package/src/ui/accordion/index.ts +0 -47
- package/src/ui/audio/Audio.stx +0 -50
- package/src/ui/audio/README.md +0 -41
- package/src/ui/audio/index.ts +0 -13
- package/src/ui/auth/Login.stx +0 -161
- package/src/ui/auth/README.md +0 -142
- package/src/ui/auth/Signup.stx +0 -159
- package/src/ui/auth/TwoFactorChallenge.stx +0 -111
- package/src/ui/auth/index.ts +0 -32
- package/src/ui/avatar/Avatar.stx +0 -77
- package/src/ui/avatar/index.ts +0 -12
- package/src/ui/badge/Badge.stx +0 -62
- package/src/ui/badge/index.ts +0 -10
- package/src/ui/breadcrumb/Breadcrumb.stx +0 -79
- package/src/ui/breadcrumb/index.ts +0 -15
- package/src/ui/button/Button.stx +0 -74
- package/src/ui/button/README.md +0 -80
- package/src/ui/button/index.ts +0 -85
- package/src/ui/calendar/Calendar.stx +0 -189
- package/src/ui/calendar/README.md +0 -254
- package/src/ui/calendar/index.ts +0 -14
- package/src/ui/card/Card.stx +0 -68
- package/src/ui/card/index.ts +0 -13
- package/src/ui/checkbox/Checkbox.stx +0 -86
- package/src/ui/checkbox/index.ts +0 -16
- package/src/ui/combobox/Combobox.stx +0 -96
- package/src/ui/combobox/ComboboxButton.stx +0 -25
- package/src/ui/combobox/ComboboxInput.stx +0 -32
- package/src/ui/combobox/ComboboxOption.stx +0 -49
- package/src/ui/combobox/ComboboxOptions.stx +0 -16
- package/src/ui/combobox/README.md +0 -150
- package/src/ui/combobox/index.ts +0 -40
- package/src/ui/command-palette/CommandPalette.stx +0 -81
- package/src/ui/command-palette/CommandPaletteItem.stx +0 -27
- package/src/ui/command-palette/README.md +0 -42
- package/src/ui/command-palette/index.ts +0 -15
- package/src/ui/dialog/Dialog.stx +0 -41
- package/src/ui/dialog/DialogBackdrop.stx +0 -9
- package/src/ui/dialog/DialogDescription.stx +0 -11
- package/src/ui/dialog/DialogPanel.stx +0 -16
- package/src/ui/dialog/DialogTitle.stx +0 -11
- package/src/ui/dialog/README.md +0 -121
- package/src/ui/dialog/index.ts +0 -32
- package/src/ui/drawer/Drawer.stx +0 -120
- package/src/ui/drawer/README.md +0 -90
- package/src/ui/drawer/index.ts +0 -9
- package/src/ui/dropdown/Dropdown.stx +0 -82
- package/src/ui/dropdown/DropdownButton.stx +0 -29
- package/src/ui/dropdown/DropdownItem.stx +0 -35
- package/src/ui/dropdown/DropdownItems.stx +0 -18
- package/src/ui/dropdown/README.md +0 -114
- package/src/ui/dropdown/index.ts +0 -29
- package/src/ui/form/Form.stx +0 -95
- package/src/ui/form/index.ts +0 -67
- package/src/ui/heatmap/Heatmap.stx +0 -294
- package/src/ui/image/Image.stx +0 -165
- package/src/ui/image/README.md +0 -164
- package/src/ui/image/index.ts +0 -22
- package/src/ui/input/EmailInput.stx +0 -30
- package/src/ui/input/NumberInput.stx +0 -126
- package/src/ui/input/PasswordInput.stx +0 -143
- package/src/ui/input/SearchInput.stx +0 -44
- package/src/ui/input/TextInput.stx +0 -132
- package/src/ui/input/index.ts +0 -180
- package/src/ui/listbox/Listbox.stx +0 -101
- package/src/ui/listbox/ListboxButton.stx +0 -28
- package/src/ui/listbox/ListboxLabel.stx +0 -11
- package/src/ui/listbox/ListboxOption.stx +0 -49
- package/src/ui/listbox/ListboxOptions.stx +0 -16
- package/src/ui/listbox/README.md +0 -135
- package/src/ui/listbox/index.ts +0 -39
- package/src/ui/navigator/Navigator.stx +0 -113
- package/src/ui/navigator/README.md +0 -352
- package/src/ui/navigator/index.ts +0 -20
- package/src/ui/notification/Notification.stx +0 -117
- package/src/ui/notification/README.md +0 -114
- package/src/ui/notification/index.ts +0 -12
- package/src/ui/pagination/Pagination.stx +0 -150
- package/src/ui/pagination/index.ts +0 -11
- package/src/ui/payment/Checkout.stx +0 -31
- package/src/ui/payment/DefaultPaymentMethod.stx +0 -62
- package/src/ui/payment/PaymentMethods.stx +0 -104
- package/src/ui/payment/README.md +0 -345
- package/src/ui/payment/SubscriptionCheckout.stx +0 -323
- package/src/ui/payment/index.ts +0 -59
- package/src/ui/popover/Popover.stx +0 -60
- package/src/ui/popover/PopoverButton.stx +0 -29
- package/src/ui/popover/PopoverPanel.stx +0 -23
- package/src/ui/popover/README.md +0 -91
- package/src/ui/popover/index.ts +0 -22
- package/src/ui/portal/Portal.stx +0 -57
- package/src/ui/portal/index.ts +0 -6
- package/src/ui/progress/Progress.stx +0 -131
- package/src/ui/progress/index.ts +0 -12
- package/src/ui/radio/Radio.stx +0 -77
- package/src/ui/radio/index.ts +0 -15
- package/src/ui/radio-group/README.md +0 -139
- package/src/ui/radio-group/RadioGroup.stx +0 -60
- package/src/ui/radio-group/RadioGroupDescription.stx +0 -11
- package/src/ui/radio-group/RadioGroupLabel.stx +0 -11
- package/src/ui/radio-group/RadioGroupOption.stx +0 -58
- package/src/ui/radio-group/index.ts +0 -30
- package/src/ui/select/README.md +0 -180
- package/src/ui/select/Select.stx +0 -82
- package/src/ui/select/index.ts +0 -21
- package/src/ui/sidebar/Sidebar.stx +0 -458
- package/src/ui/sidebar/SidebarFooter.stx +0 -71
- package/src/ui/sidebar/SidebarHeader.stx +0 -103
- package/src/ui/sidebar/SidebarItem.stx +0 -86
- package/src/ui/sidebar/SidebarSection.stx +0 -83
- package/src/ui/sidebar/index.ts +0 -153
- package/src/ui/sidebar/themes.ts +0 -240
- package/src/ui/skeleton/Skeleton.stx +0 -48
- package/src/ui/skeleton/index.ts +0 -11
- package/src/ui/spinner/Spinner.stx +0 -109
- package/src/ui/spinner/index.ts +0 -9
- package/src/ui/stepper/README.md +0 -36
- package/src/ui/stepper/Stepper.stx +0 -17
- package/src/ui/stepper/StepperStep.stx +0 -57
- package/src/ui/stepper/index.ts +0 -19
- package/src/ui/storage/README.md +0 -193
- package/src/ui/storage/index.ts +0 -347
- package/src/ui/switch/README.md +0 -114
- package/src/ui/switch/Switch.stx +0 -72
- package/src/ui/switch/index.ts +0 -10
- package/src/ui/table/README.md +0 -39
- package/src/ui/table/Table.stx +0 -14
- package/src/ui/table/TableBody.stx +0 -10
- package/src/ui/table/TableCell.stx +0 -10
- package/src/ui/table/TableHead.stx +0 -10
- package/src/ui/table/TableHeader.stx +0 -20
- package/src/ui/table/TableRow.stx +0 -11
- package/src/ui/table/index.ts +0 -35
- package/src/ui/tabs/TabPanel.stx +0 -25
- package/src/ui/tabs/Tabs.stx +0 -202
- package/src/ui/tabs/index.ts +0 -50
- package/src/ui/teleport/Teleport.stx +0 -65
- package/src/ui/teleport/index.ts +0 -7
- package/src/ui/textarea/Textarea.stx +0 -108
- package/src/ui/textarea/index.ts +0 -20
- package/src/ui/tooltip/Tooltip.stx +0 -71
- package/src/ui/tooltip/index.ts +0 -10
- package/src/ui/transition/README.md +0 -30
- package/src/ui/transition/Transition.stx +0 -102
- package/src/ui/transition/index.ts +0 -25
- package/src/ui/video/README.md +0 -47
- package/src/ui/video/Video.stx +0 -45
- package/src/ui/video/index.ts +0 -16
- package/src/ui/virtual-list/VirtualList.stx +0 -67
- package/src/ui/virtual-list/index.ts +0 -10
- package/src/ui/virtual-table/VirtualTable.stx +0 -103
- package/src/ui/virtual-table/index.ts +0 -21
- package/src/utils/PROP_VALIDATION.md +0 -458
- package/src/utils/THEME_SYSTEM.md +0 -702
- package/src/utils/accessibility.ts +0 -841
- package/src/utils/animation/easing.ts +0 -368
- package/src/utils/animation/helpers.ts +0 -223
- package/src/utils/animation/index.ts +0 -5
- package/src/utils/animation/keyframes.ts +0 -230
- package/src/utils/error-handling.ts +0 -475
- package/src/utils/highlighter.ts +0 -93
- package/src/utils/i18n.ts +0 -634
- package/src/utils/keep-alive.ts +0 -819
- package/src/utils/lazy-loading.ts +0 -530
- package/src/utils/prop-validation.ts +0 -478
- package/src/utils/theme.ts +0 -626
package/src/ui/form/Form.stx
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
export const action = $props.action || ''
|
|
3
|
-
export const method = $props.method || 'POST'
|
|
4
|
-
export const validationSchema = $props.validationSchema || {}
|
|
5
|
-
export const className = $props.className || ''
|
|
6
|
-
|
|
7
|
-
export const formClasses = `space-y-6 ${className}`.trim()
|
|
8
|
-
export const errorClasses = `mt-1 text-sm text-red-600 dark:text-red-400`.trim()
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<script client>
|
|
12
|
-
const emit = defineEmits()
|
|
13
|
-
const validationSchema = {{ validationSchema }}
|
|
14
|
-
|
|
15
|
-
const errors = state({})
|
|
16
|
-
const isSubmitting = state(false)
|
|
17
|
-
const submitCount = state(0)
|
|
18
|
-
|
|
19
|
-
function validateField(name, value, allValues) {
|
|
20
|
-
const schema = validationSchema[name]
|
|
21
|
-
if (!schema) return null
|
|
22
|
-
if (schema.required && !value) {
|
|
23
|
-
return schema.requiredMessage || `${name} is required`
|
|
24
|
-
}
|
|
25
|
-
if (schema.minLength && String(value).length < schema.minLength) {
|
|
26
|
-
return schema.minLengthMessage || `${name} must be at least ${schema.minLength} characters`
|
|
27
|
-
}
|
|
28
|
-
if (schema.maxLength && String(value).length > schema.maxLength) {
|
|
29
|
-
return schema.maxLengthMessage || `${name} must be at most ${schema.maxLength} characters`
|
|
30
|
-
}
|
|
31
|
-
if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
|
|
32
|
-
return schema.patternMessage || `${name} is invalid`
|
|
33
|
-
}
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function validateForm(values) {
|
|
38
|
-
const next = {}
|
|
39
|
-
let valid = true
|
|
40
|
-
for (const name of Object.keys(validationSchema)) {
|
|
41
|
-
const err = validateField(name, values[name], values)
|
|
42
|
-
if (err) {
|
|
43
|
-
next[name] = err
|
|
44
|
-
valid = false
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
errors.set(next)
|
|
48
|
-
return valid
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function onSubmit(event) {
|
|
52
|
-
event.preventDefault()
|
|
53
|
-
submitCount.set(submitCount() + 1)
|
|
54
|
-
|
|
55
|
-
const formData = new FormData(event.target)
|
|
56
|
-
const values = {}
|
|
57
|
-
for (const [k, v] of formData.entries()) values[k] = v
|
|
58
|
-
|
|
59
|
-
if (!validateForm(values)) {
|
|
60
|
-
emit('error', errors())
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
isSubmitting.set(true)
|
|
65
|
-
try {
|
|
66
|
-
emit('submit', values)
|
|
67
|
-
}
|
|
68
|
-
finally {
|
|
69
|
-
isSubmitting.set(false)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function setErrors(next) {
|
|
74
|
-
errors.set({ ...errors(), ...next })
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function reset(form) {
|
|
78
|
-
errors.set({})
|
|
79
|
-
isSubmitting.set(false)
|
|
80
|
-
submitCount.set(0)
|
|
81
|
-
if (form?.reset) form.reset()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
defineExpose({ errors, isSubmitting, submitCount, setErrors, reset, validateForm })
|
|
85
|
-
</script>
|
|
86
|
-
|
|
87
|
-
<form
|
|
88
|
-
class="{{ formClasses }}"
|
|
89
|
-
@if(action)action="{{ action }}"@endif
|
|
90
|
-
method="{{ method }}"
|
|
91
|
-
@submit="onSubmit($event)"
|
|
92
|
-
novalidate
|
|
93
|
-
>
|
|
94
|
-
<slot />
|
|
95
|
-
</form>
|
package/src/ui/form/index.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { PropSchema, ValidationResult } from '../../utils/prop-validation'
|
|
2
|
-
import { createPropValidator, PropTypes } from '../../utils/prop-validation'
|
|
3
|
-
|
|
4
|
-
export { default as Form } from './Form.stx'
|
|
5
|
-
|
|
6
|
-
export interface FieldSchema {
|
|
7
|
-
required?: boolean
|
|
8
|
-
requiredMessage?: string
|
|
9
|
-
minLength?: number
|
|
10
|
-
minLengthMessage?: string
|
|
11
|
-
maxLength?: number
|
|
12
|
-
maxLengthMessage?: string
|
|
13
|
-
pattern?: RegExp
|
|
14
|
-
patternMessage?: string
|
|
15
|
-
validate?: (value: any, values: Record<string, any>) => string | null | undefined
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface ValidationSchema {
|
|
19
|
-
[fieldName: string]: FieldSchema
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface FormHelpers {
|
|
23
|
-
setErrors: (errors: Record<string, string>) => void
|
|
24
|
-
setValues: (values: Record<string, any>) => void
|
|
25
|
-
resetForm: () => void
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface FormProps {
|
|
29
|
-
action?: string
|
|
30
|
-
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
31
|
-
validationSchema?: ValidationSchema
|
|
32
|
-
initialValues?: Record<string, any>
|
|
33
|
-
validateOnChange?: boolean
|
|
34
|
-
validateOnBlur?: boolean
|
|
35
|
-
onSubmit?: (values: Record<string, any>, helpers: FormHelpers) => void | Promise<void>
|
|
36
|
-
onError?: (errors: Record<string, string> | Error) => void
|
|
37
|
-
className?: string
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface FieldProps {
|
|
41
|
-
name: string
|
|
42
|
-
value: any
|
|
43
|
-
error?: string
|
|
44
|
-
touched?: boolean
|
|
45
|
-
onChange: (event: Event) => void
|
|
46
|
-
onBlur: (event: Event) => void
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Form prop validation schema
|
|
51
|
-
*/
|
|
52
|
-
export const formPropSchema: PropSchema = {
|
|
53
|
-
action: PropTypes.string,
|
|
54
|
-
method: PropTypes.oneOf(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
|
|
55
|
-
validationSchema: PropTypes.object,
|
|
56
|
-
initialValues: PropTypes.object,
|
|
57
|
-
validateOnChange: PropTypes.boolean,
|
|
58
|
-
validateOnBlur: PropTypes.boolean,
|
|
59
|
-
onSubmit: PropTypes.func,
|
|
60
|
-
onError: PropTypes.func,
|
|
61
|
-
className: PropTypes.string,
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Validate Form component props
|
|
66
|
-
*/
|
|
67
|
-
export const validateFormProps: (props: Record<string, any>) => ValidationResult = createPropValidator('Form', formPropSchema)
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
export const id = $props.id || `stx-heatmap-${Math.random().toString(36).slice(2, 8)}`
|
|
3
|
-
export const mode = $props.mode || 'overlay'
|
|
4
|
-
export const width = $props.width || null
|
|
5
|
-
export const height = $props.height || null
|
|
6
|
-
export const radius = $props.radius || 20
|
|
7
|
-
export const blur = $props.blur || 15
|
|
8
|
-
export const opacity = $props.opacity || 0.6
|
|
9
|
-
export const showLegend = $props.showLegend !== false
|
|
10
|
-
export const interactive = $props.interactive || false
|
|
11
|
-
export const dataSource = $props.dataSource || 'auto'
|
|
12
|
-
export const endpoint = $props.endpoint || ''
|
|
13
|
-
export const colorScheme = $props.colorScheme || 'default'
|
|
14
|
-
|
|
15
|
-
const colorSchemes = {
|
|
16
|
-
default: ['#0000ff', '#00ffff', '#00ff00', '#ffff00', '#ff0000'],
|
|
17
|
-
fire: ['#000000', '#ff0000', '#ffff00', '#ffffff'],
|
|
18
|
-
cool: ['#000033', '#0066ff', '#00ccff', '#ffffff'],
|
|
19
|
-
grayscale: ['#000000', '#444444', '#888888', '#cccccc', '#ffffff'],
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const colors = colorSchemes[colorScheme] || colorSchemes.default
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<div
|
|
26
|
-
id="{{ id }}"
|
|
27
|
-
class="stx-heatmap stx-heatmap--{{ mode }}"
|
|
28
|
-
data-heatmap-radius="{{ radius }}"
|
|
29
|
-
data-heatmap-blur="{{ blur }}"
|
|
30
|
-
data-heatmap-opacity="{{ opacity }}"
|
|
31
|
-
data-heatmap-colors="{{ JSON.stringify(colors) }}"
|
|
32
|
-
data-heatmap-source="{{ dataSource }}"
|
|
33
|
-
@if(endpoint)
|
|
34
|
-
data-heatmap-endpoint="{{ endpoint }}"
|
|
35
|
-
@endif
|
|
36
|
-
@if(width)
|
|
37
|
-
style="width: {{ width }}px; {{ height ? 'height: ' + height + 'px;' : '' }}"
|
|
38
|
-
@endif
|
|
39
|
-
>
|
|
40
|
-
<canvas class="stx-heatmap__canvas"></canvas>
|
|
41
|
-
|
|
42
|
-
@if(showLegend)
|
|
43
|
-
<div class="stx-heatmap__legend">
|
|
44
|
-
<span class="stx-heatmap__legend-label">Low</span>
|
|
45
|
-
<div class="stx-heatmap__legend-gradient" style="background: linear-gradient(to right, {{ colors.join(', ') }})"></div>
|
|
46
|
-
<span class="stx-heatmap__legend-label">High</span>
|
|
47
|
-
</div>
|
|
48
|
-
@endif
|
|
49
|
-
|
|
50
|
-
@if(interactive)
|
|
51
|
-
<div class="stx-heatmap__controls">
|
|
52
|
-
<button type="button" class="stx-heatmap__control" data-action="zoom-in" aria-label="Zoom in">+</button>
|
|
53
|
-
<button type="button" class="stx-heatmap__control" data-action="zoom-out" aria-label="Zoom out">-</button>
|
|
54
|
-
<button type="button" class="stx-heatmap__control" data-action="reset" aria-label="Reset view">Reset</button>
|
|
55
|
-
</div>
|
|
56
|
-
@endif
|
|
57
|
-
|
|
58
|
-
{{ slot }}
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<style>
|
|
62
|
-
.stx-heatmap {
|
|
63
|
-
position: relative;
|
|
64
|
-
overflow: hidden;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.stx-heatmap--overlay {
|
|
68
|
-
position: fixed;
|
|
69
|
-
top: 0;
|
|
70
|
-
left: 0;
|
|
71
|
-
width: 100%;
|
|
72
|
-
height: 100%;
|
|
73
|
-
pointer-events: none;
|
|
74
|
-
z-index: 9999;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.stx-heatmap--standalone {
|
|
78
|
-
display: block;
|
|
79
|
-
border: 1px solid #e0e0e0;
|
|
80
|
-
border-radius: 8px;
|
|
81
|
-
background: #f9f9f9;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.stx-heatmap--thumbnail {
|
|
85
|
-
display: inline-block;
|
|
86
|
-
border: 1px solid #e0e0e0;
|
|
87
|
-
border-radius: 4px;
|
|
88
|
-
background: #f9f9f9;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.stx-heatmap__canvas {
|
|
92
|
-
width: 100%;
|
|
93
|
-
height: 100%;
|
|
94
|
-
display: block;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.stx-heatmap__legend {
|
|
98
|
-
position: absolute;
|
|
99
|
-
bottom: 10px;
|
|
100
|
-
left: 50%;
|
|
101
|
-
transform: translateX(-50%);
|
|
102
|
-
display: flex;
|
|
103
|
-
align-items: center;
|
|
104
|
-
gap: 8px;
|
|
105
|
-
padding: 6px 12px;
|
|
106
|
-
background: rgba(255, 255, 255, 0.9);
|
|
107
|
-
border-radius: 4px;
|
|
108
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
109
|
-
font-size: 12px;
|
|
110
|
-
pointer-events: auto;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.stx-heatmap__legend-gradient {
|
|
114
|
-
width: 100px;
|
|
115
|
-
height: 12px;
|
|
116
|
-
border-radius: 2px;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.stx-heatmap__legend-label {
|
|
120
|
-
color: #666;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.stx-heatmap__controls {
|
|
124
|
-
position: absolute;
|
|
125
|
-
top: 10px;
|
|
126
|
-
right: 10px;
|
|
127
|
-
display: flex;
|
|
128
|
-
flex-direction: column;
|
|
129
|
-
gap: 4px;
|
|
130
|
-
pointer-events: auto;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.stx-heatmap__control {
|
|
134
|
-
width: 32px;
|
|
135
|
-
height: 32px;
|
|
136
|
-
display: flex;
|
|
137
|
-
align-items: center;
|
|
138
|
-
justify-content: center;
|
|
139
|
-
border: 1px solid #e0e0e0;
|
|
140
|
-
border-radius: 4px;
|
|
141
|
-
background: rgba(255, 255, 255, 0.9);
|
|
142
|
-
cursor: pointer;
|
|
143
|
-
font-size: 16px;
|
|
144
|
-
transition: background-color 0.2s;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.stx-heatmap__control:hover {
|
|
148
|
-
background: #f0f0f0;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.stx-heatmap__control:active {
|
|
152
|
-
background: #e0e0e0;
|
|
153
|
-
}
|
|
154
|
-
</style>
|
|
155
|
-
|
|
156
|
-
<script client>
|
|
157
|
-
(function() {
|
|
158
|
-
const container = document.getElementById('{{ id }}');
|
|
159
|
-
if (!container) return;
|
|
160
|
-
|
|
161
|
-
const canvas = container.querySelector('.stx-heatmap__canvas');
|
|
162
|
-
const ctx = canvas.getContext('2d');
|
|
163
|
-
|
|
164
|
-
const config = {
|
|
165
|
-
radius: parseInt(container.dataset.heatmapRadius) || 20,
|
|
166
|
-
blur: parseInt(container.dataset.heatmapBlur) || 15,
|
|
167
|
-
opacity: parseFloat(container.dataset.heatmapOpacity) || 0.6,
|
|
168
|
-
colors: JSON.parse(container.dataset.heatmapColors || '[]'),
|
|
169
|
-
source: container.dataset.heatmapSource || 'auto',
|
|
170
|
-
endpoint: container.dataset.heatmapEndpoint || ''
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// Resize canvas to match container
|
|
174
|
-
function resizeCanvas() {
|
|
175
|
-
const rect = container.getBoundingClientRect();
|
|
176
|
-
canvas.width = rect.width || window.innerWidth;
|
|
177
|
-
canvas.height = rect.height || window.innerHeight;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Create color gradient for heatmap
|
|
181
|
-
function createGradient(colors) {
|
|
182
|
-
const gradientCanvas = document.createElement('canvas');
|
|
183
|
-
gradientCanvas.width = 256;
|
|
184
|
-
gradientCanvas.height = 1;
|
|
185
|
-
const gradientCtx = gradientCanvas.getContext('2d');
|
|
186
|
-
const gradient = gradientCtx.createLinearGradient(0, 0, 256, 0);
|
|
187
|
-
|
|
188
|
-
colors.forEach((color, i) => {
|
|
189
|
-
gradient.addColorStop(i / (colors.length - 1), color);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
gradientCtx.fillStyle = gradient;
|
|
193
|
-
gradientCtx.fillRect(0, 0, 256, 1);
|
|
194
|
-
return gradientCtx.getImageData(0, 0, 256, 1).data;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Render heatmap from data points
|
|
198
|
-
function render(points) {
|
|
199
|
-
resizeCanvas();
|
|
200
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
201
|
-
|
|
202
|
-
// Draw heat points
|
|
203
|
-
points.forEach(function(p) {
|
|
204
|
-
const x = (p.x / 100) * canvas.width;
|
|
205
|
-
const y = (p.y / 100) * canvas.height;
|
|
206
|
-
const intensity = p.type === 'click' ? 1 : 0.3;
|
|
207
|
-
|
|
208
|
-
const gradient = ctx.createRadialGradient(x, y, 0, x, y, config.radius);
|
|
209
|
-
gradient.addColorStop(0, 'rgba(0,0,0,' + intensity + ')');
|
|
210
|
-
gradient.addColorStop(1, 'rgba(0,0,0,0)');
|
|
211
|
-
ctx.fillStyle = gradient;
|
|
212
|
-
ctx.fillRect(x - config.radius, y - config.radius, config.radius * 2, config.radius * 2);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// Apply blur
|
|
216
|
-
if (config.blur > 0) {
|
|
217
|
-
ctx.filter = 'blur(' + config.blur + 'px)';
|
|
218
|
-
ctx.drawImage(canvas, 0, 0);
|
|
219
|
-
ctx.filter = 'none';
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Colorize
|
|
223
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
224
|
-
const pixels = imageData.data;
|
|
225
|
-
const gradientColors = createGradient(config.colors);
|
|
226
|
-
|
|
227
|
-
for (let i = 0; i < pixels.length; i += 4) {
|
|
228
|
-
const alpha = pixels[i + 3];
|
|
229
|
-
if (alpha > 0) {
|
|
230
|
-
const colorIndex = Math.min(alpha, 255) * 4;
|
|
231
|
-
pixels[i] = gradientColors[colorIndex];
|
|
232
|
-
pixels[i + 1] = gradientColors[colorIndex + 1];
|
|
233
|
-
pixels[i + 2] = gradientColors[colorIndex + 2];
|
|
234
|
-
pixels[i + 3] = Math.round(alpha * config.opacity);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
ctx.putImageData(imageData, 0, 0);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Load data based on source
|
|
242
|
-
function loadData() {
|
|
243
|
-
if (config.source === 'auto' && window.stxHeatmap) {
|
|
244
|
-
render(window.stxHeatmap.getData().points);
|
|
245
|
-
} else if (config.source === 'endpoint' && config.endpoint) {
|
|
246
|
-
fetch(config.endpoint)
|
|
247
|
-
.then(r => r.json())
|
|
248
|
-
.then(data => render(data.points || data))
|
|
249
|
-
.catch(console.error);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Initialize
|
|
254
|
-
resizeCanvas();
|
|
255
|
-
loadData();
|
|
256
|
-
|
|
257
|
-
// Handle resize
|
|
258
|
-
window.addEventListener('resize', function() {
|
|
259
|
-
resizeCanvas();
|
|
260
|
-
loadData();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// Handle interactive controls
|
|
264
|
-
const controls = container.querySelectorAll('.stx-heatmap__control');
|
|
265
|
-
let zoom = 1;
|
|
266
|
-
let panX = 0;
|
|
267
|
-
let panY = 0;
|
|
268
|
-
|
|
269
|
-
controls.forEach(function(btn) {
|
|
270
|
-
btn.addEventListener('click', function() {
|
|
271
|
-
const action = btn.dataset.action;
|
|
272
|
-
if (action === 'zoom-in') {
|
|
273
|
-
zoom = Math.min(zoom * 1.2, 5);
|
|
274
|
-
} else if (action === 'zoom-out') {
|
|
275
|
-
zoom = Math.max(zoom / 1.2, 0.5);
|
|
276
|
-
} else if (action === 'reset') {
|
|
277
|
-
zoom = 1;
|
|
278
|
-
panX = 0;
|
|
279
|
-
panY = 0;
|
|
280
|
-
}
|
|
281
|
-
canvas.style.transform = 'scale(' + zoom + ') translate(' + panX + 'px, ' + panY + 'px)';
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// Expose API
|
|
286
|
-
container.stxHeatmap = {
|
|
287
|
-
render: render,
|
|
288
|
-
refresh: loadData,
|
|
289
|
-
setData: function(points) { render(points); },
|
|
290
|
-
getCanvas: function() { return canvas; },
|
|
291
|
-
toDataURL: function() { return canvas.toDataURL(); }
|
|
292
|
-
};
|
|
293
|
-
})();
|
|
294
|
-
</script>
|
package/src/ui/image/Image.stx
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
export const src = $props.src || ''
|
|
3
|
-
export const srcSet = $props.srcSet || ''
|
|
4
|
-
export const sizes = $props.sizes || ''
|
|
5
|
-
export const webpSrc = $props.webpSrc || ''
|
|
6
|
-
export const webpSrcSet = $props.webpSrcSet || ''
|
|
7
|
-
export const alt = $props.alt || ''
|
|
8
|
-
export const width = $props.width
|
|
9
|
-
export const height = $props.height
|
|
10
|
-
export const lazy = $props.lazy ?? true
|
|
11
|
-
export const placeholder = $props.placeholder || 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300"%3E%3Crect fill="%23e5e7eb" width="400" height="300"/%3E%3C/svg%3E'
|
|
12
|
-
export const blurDataURL = $props.blurDataURL || ''
|
|
13
|
-
export const aspectRatio = $props.aspectRatio
|
|
14
|
-
export const objectFit = $props.objectFit || 'cover'
|
|
15
|
-
export const rounded = $props.rounded ?? false
|
|
16
|
-
export const zoomOnHover = $props.zoomOnHover || false
|
|
17
|
-
export const className = $props.className || ''
|
|
18
|
-
|
|
19
|
-
export const hasWebP = !!(webpSrc || webpSrcSet)
|
|
20
|
-
|
|
21
|
-
const objectFitClasses = {
|
|
22
|
-
contain: 'object-contain',
|
|
23
|
-
cover: 'object-cover',
|
|
24
|
-
fill: 'object-fill',
|
|
25
|
-
none: 'object-none',
|
|
26
|
-
'scale-down': 'object-scale-down',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const roundedClass = rounded === true ? 'rounded-lg' : rounded === 'full' ? 'rounded-full' : rounded ? `rounded-${rounded}` : ''
|
|
30
|
-
const zoomClasses = zoomOnHover ? 'transform hover:scale-110 transition-transform duration-300 ease-in-out cursor-zoom-in' : ''
|
|
31
|
-
|
|
32
|
-
export const containerClasses = `relative overflow-hidden ${roundedClass} ${className}`.trim()
|
|
33
|
-
export const imageClasses = `w-full h-full transition-opacity duration-300 ${objectFitClasses[objectFit]} ${zoomClasses}`.trim()
|
|
34
|
-
export const blurPlaceholderClasses = `absolute inset-0 ${roundedClass} filter blur-lg scale-110`.trim()
|
|
35
|
-
export const containerStyle = aspectRatio ? `padding-bottom: ${(1 / aspectRatio) * 100}%` : ''
|
|
36
|
-
</script>
|
|
37
|
-
|
|
38
|
-
<script client>
|
|
39
|
-
const emit = defineEmits()
|
|
40
|
-
const lazy = {{ lazy }}
|
|
41
|
-
const realSrc = {{ src }}
|
|
42
|
-
const realSrcSet = {{ srcSet }}
|
|
43
|
-
const errorPlaceholder = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300"%3E%3Crect fill="%23e5e7eb" width="400" height="300"/%3E%3Ctext x="50%25" y="50%25" fill="%239ca3af" font-size="16" text-anchor="middle" dominant-baseline="middle"%3EImage not found%3C/text%3E%3C/svg%3E'
|
|
44
|
-
|
|
45
|
-
function onImageLoad(event) {
|
|
46
|
-
const img = event.target
|
|
47
|
-
img.classList.remove('opacity-0')
|
|
48
|
-
img.classList.add('opacity-100')
|
|
49
|
-
emit('load', event)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function onImageError(event) {
|
|
53
|
-
const img = event.target
|
|
54
|
-
img.src = errorPlaceholder
|
|
55
|
-
emit('error', event)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
onMount(() => {
|
|
59
|
-
if (!lazy) return
|
|
60
|
-
if (typeof IntersectionObserver === 'undefined') return
|
|
61
|
-
const lazyImages = document.querySelectorAll('.lazy-image:not([data-stx-img-loaded])')
|
|
62
|
-
const observer = new IntersectionObserver((entries, obs) => {
|
|
63
|
-
for (const entry of entries) {
|
|
64
|
-
if (!entry.isIntersecting) continue
|
|
65
|
-
const img = entry.target
|
|
66
|
-
img.setAttribute('data-stx-img-loaded', '1')
|
|
67
|
-
const dataSrc = img.getAttribute('data-src')
|
|
68
|
-
const dataSrcSet = img.getAttribute('data-srcset')
|
|
69
|
-
if (!dataSrc) continue
|
|
70
|
-
const probe = new Image()
|
|
71
|
-
probe.onload = () => {
|
|
72
|
-
img.src = dataSrc
|
|
73
|
-
if (dataSrcSet) img.srcset = dataSrcSet
|
|
74
|
-
img.classList.remove('opacity-0')
|
|
75
|
-
img.classList.add('opacity-100')
|
|
76
|
-
obs.unobserve(img)
|
|
77
|
-
}
|
|
78
|
-
probe.onerror = () => {
|
|
79
|
-
onImageError({ target: img })
|
|
80
|
-
obs.unobserve(img)
|
|
81
|
-
}
|
|
82
|
-
probe.src = dataSrc
|
|
83
|
-
if (dataSrcSet) probe.srcset = dataSrcSet
|
|
84
|
-
}
|
|
85
|
-
}, { rootMargin: '50px' })
|
|
86
|
-
for (const img of lazyImages) observer.observe(img)
|
|
87
|
-
})
|
|
88
|
-
</script>
|
|
89
|
-
|
|
90
|
-
<div class="{{ containerClasses }}" style="{{ containerStyle }}">
|
|
91
|
-
@if(blurDataURL)
|
|
92
|
-
<img
|
|
93
|
-
src="{{ blurDataURL }}"
|
|
94
|
-
alt=""
|
|
95
|
-
class="{{ blurPlaceholderClasses }}"
|
|
96
|
-
aria-hidden="true"
|
|
97
|
-
/>
|
|
98
|
-
@endif
|
|
99
|
-
|
|
100
|
-
@if(hasWebP)
|
|
101
|
-
<picture>
|
|
102
|
-
<source
|
|
103
|
-
type="image/webp"
|
|
104
|
-
@if(webpSrcSet)srcset="{{ webpSrcSet }}"@endif
|
|
105
|
-
@if(webpSrc && !webpSrcSet)srcset="{{ webpSrc }}"@endif
|
|
106
|
-
@if(sizes)sizes="{{ sizes }}"@endif
|
|
107
|
-
/>
|
|
108
|
-
@if(lazy)
|
|
109
|
-
<img
|
|
110
|
-
src="{{ placeholder }}"
|
|
111
|
-
data-src="{{ src }}"
|
|
112
|
-
@if(srcSet)data-srcset="{{ srcSet }}"@endif
|
|
113
|
-
@if(sizes)sizes="{{ sizes }}"@endif
|
|
114
|
-
alt="{{ alt }}"
|
|
115
|
-
class="{{ imageClasses }} lazy-image opacity-0"
|
|
116
|
-
width="{{ width }}"
|
|
117
|
-
height="{{ height }}"
|
|
118
|
-
loading="lazy"
|
|
119
|
-
@load="onImageLoad($event)"
|
|
120
|
-
@error="onImageError($event)"
|
|
121
|
-
/>
|
|
122
|
-
@else
|
|
123
|
-
<img
|
|
124
|
-
src="{{ src }}"
|
|
125
|
-
@if(srcSet)srcset="{{ srcSet }}"@endif
|
|
126
|
-
@if(sizes)sizes="{{ sizes }}"@endif
|
|
127
|
-
alt="{{ alt }}"
|
|
128
|
-
class="{{ imageClasses }}"
|
|
129
|
-
width="{{ width }}"
|
|
130
|
-
height="{{ height }}"
|
|
131
|
-
@load="onImageLoad($event)"
|
|
132
|
-
@error="onImageError($event)"
|
|
133
|
-
/>
|
|
134
|
-
@endif
|
|
135
|
-
</picture>
|
|
136
|
-
@else
|
|
137
|
-
@if(lazy)
|
|
138
|
-
<img
|
|
139
|
-
src="{{ placeholder }}"
|
|
140
|
-
data-src="{{ src }}"
|
|
141
|
-
@if(srcSet)data-srcset="{{ srcSet }}"@endif
|
|
142
|
-
@if(sizes)sizes="{{ sizes }}"@endif
|
|
143
|
-
alt="{{ alt }}"
|
|
144
|
-
class="{{ imageClasses }} lazy-image opacity-0"
|
|
145
|
-
width="{{ width }}"
|
|
146
|
-
height="{{ height }}"
|
|
147
|
-
loading="lazy"
|
|
148
|
-
@load="onImageLoad($event)"
|
|
149
|
-
@error="onImageError($event)"
|
|
150
|
-
/>
|
|
151
|
-
@else
|
|
152
|
-
<img
|
|
153
|
-
src="{{ src }}"
|
|
154
|
-
@if(srcSet)srcset="{{ srcSet }}"@endif
|
|
155
|
-
@if(sizes)sizes="{{ sizes }}"@endif
|
|
156
|
-
alt="{{ alt }}"
|
|
157
|
-
class="{{ imageClasses }}"
|
|
158
|
-
width="{{ width }}"
|
|
159
|
-
height="{{ height }}"
|
|
160
|
-
@load="onImageLoad($event)"
|
|
161
|
-
@error="onImageError($event)"
|
|
162
|
-
/>
|
|
163
|
-
@endif
|
|
164
|
-
@endif
|
|
165
|
-
</div>
|