@proyecto-viviana/solidaria 0.2.2 → 0.2.4
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/autocomplete/createAutocomplete.d.ts +2 -2
- package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
- package/dist/index.js +233 -234
- package/dist/index.js.map +2 -2
- package/dist/index.ssr.js +233 -234
- package/dist/index.ssr.js.map +2 -2
- package/dist/interactions/PressEvent.d.ts +13 -10
- package/dist/interactions/PressEvent.d.ts.map +1 -1
- package/dist/interactions/createPress.d.ts.map +1 -1
- package/dist/interactions/index.d.ts +1 -1
- package/dist/interactions/index.d.ts.map +1 -1
- package/dist/select/createHiddenSelect.d.ts.map +1 -1
- package/dist/toolbar/createToolbar.d.ts.map +1 -1
- package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
- package/package.json +9 -7
- package/src/autocomplete/createAutocomplete.ts +341 -0
- package/src/autocomplete/index.ts +9 -0
- package/src/breadcrumbs/createBreadcrumbs.ts +196 -0
- package/src/breadcrumbs/index.ts +8 -0
- package/src/button/createButton.ts +142 -0
- package/src/button/createToggleButton.ts +101 -0
- package/src/button/index.ts +4 -0
- package/src/button/types.ts +78 -0
- package/src/calendar/createCalendar.ts +138 -0
- package/src/calendar/createCalendarCell.ts +187 -0
- package/src/calendar/createCalendarGrid.ts +140 -0
- package/src/calendar/createRangeCalendar.ts +136 -0
- package/src/calendar/createRangeCalendarCell.ts +186 -0
- package/src/calendar/index.ts +34 -0
- package/src/checkbox/createCheckbox.ts +135 -0
- package/src/checkbox/createCheckboxGroup.ts +137 -0
- package/src/checkbox/createCheckboxGroupItem.ts +117 -0
- package/src/checkbox/createCheckboxGroupState.ts +193 -0
- package/src/checkbox/index.ts +13 -0
- package/src/color/createColorArea.ts +314 -0
- package/src/color/createColorField.ts +137 -0
- package/src/color/createColorSlider.ts +197 -0
- package/src/color/createColorSwatch.ts +40 -0
- package/src/color/createColorWheel.ts +208 -0
- package/src/color/index.ts +24 -0
- package/src/color/types.ts +116 -0
- package/src/combobox/createComboBox.ts +647 -0
- package/src/combobox/index.ts +6 -0
- package/src/combobox/intl/en-US.json +7 -0
- package/src/combobox/intl/es-ES.json +7 -0
- package/src/combobox/intl/index.ts +23 -0
- package/src/datepicker/createDateField.ts +154 -0
- package/src/datepicker/createDatePicker.ts +206 -0
- package/src/datepicker/createDateSegment.ts +229 -0
- package/src/datepicker/createTimeField.ts +154 -0
- package/src/datepicker/index.ts +28 -0
- package/src/dialog/createDialog.ts +120 -0
- package/src/dialog/index.ts +2 -0
- package/src/dialog/types.ts +19 -0
- package/src/disclosure/createDisclosure.ts +131 -0
- package/src/disclosure/createDisclosureGroup.ts +62 -0
- package/src/disclosure/index.ts +11 -0
- package/src/dnd/createDrag.ts +209 -0
- package/src/dnd/createDraggableCollection.ts +63 -0
- package/src/dnd/createDraggableItem.ts +243 -0
- package/src/dnd/createDrop.ts +321 -0
- package/src/dnd/createDroppableCollection.ts +293 -0
- package/src/dnd/createDroppableItem.ts +213 -0
- package/src/dnd/index.ts +47 -0
- package/src/dnd/types.ts +89 -0
- package/src/dnd/utils.ts +294 -0
- package/src/focus/FocusScope.tsx +408 -0
- package/src/focus/createAutoFocus.ts +321 -0
- package/src/focus/createFocusRestore.ts +313 -0
- package/src/focus/createVirtualFocus.ts +396 -0
- package/src/focus/index.ts +35 -0
- package/src/form/createFormReset.ts +51 -0
- package/src/form/createFormValidation.ts +224 -0
- package/src/form/index.ts +11 -0
- package/src/grid/GridKeyboardDelegate.ts +429 -0
- package/src/grid/createGrid.ts +261 -0
- package/src/grid/createGridCell.ts +182 -0
- package/src/grid/createGridRow.ts +153 -0
- package/src/grid/index.ts +18 -0
- package/src/grid/types.ts +133 -0
- package/src/gridlist/createGridList.ts +185 -0
- package/src/gridlist/createGridListItem.ts +180 -0
- package/src/gridlist/createGridListSelectionCheckbox.ts +59 -0
- package/src/gridlist/index.ts +16 -0
- package/src/gridlist/types.ts +81 -0
- package/src/i18n/NumberFormatter.ts +266 -0
- package/src/i18n/createCollator.ts +79 -0
- package/src/i18n/createDateFormatter.ts +83 -0
- package/src/i18n/createFilter.ts +131 -0
- package/src/i18n/createNumberFormatter.ts +52 -0
- package/src/i18n/createStringFormatter.ts +87 -0
- package/src/i18n/index.ts +40 -0
- package/src/i18n/locale.tsx +188 -0
- package/src/i18n/utils.ts +99 -0
- package/src/index.ts +670 -0
- package/src/interactions/FocusableProvider.tsx +44 -0
- package/src/interactions/PressEvent.ts +126 -0
- package/src/interactions/createFocus.ts +163 -0
- package/src/interactions/createFocusRing.ts +89 -0
- package/src/interactions/createFocusWithin.ts +206 -0
- package/src/interactions/createFocusable.ts +168 -0
- package/src/interactions/createHover.ts +254 -0
- package/src/interactions/createInteractionModality.ts +424 -0
- package/src/interactions/createKeyboard.ts +82 -0
- package/src/interactions/createLongPress.ts +174 -0
- package/src/interactions/createMove.ts +289 -0
- package/src/interactions/createPress.ts +834 -0
- package/src/interactions/index.ts +78 -0
- package/src/label/createField.ts +145 -0
- package/src/label/createLabel.ts +117 -0
- package/src/label/createLabels.ts +50 -0
- package/src/label/index.ts +19 -0
- package/src/landmark/createLandmark.ts +377 -0
- package/src/landmark/index.ts +8 -0
- package/src/link/createLink.ts +182 -0
- package/src/link/index.ts +1 -0
- package/src/listbox/createListBox.ts +269 -0
- package/src/listbox/createOption.ts +151 -0
- package/src/listbox/index.ts +12 -0
- package/src/live-announcer/announce.ts +322 -0
- package/src/live-announcer/index.ts +9 -0
- package/src/menu/createMenu.ts +396 -0
- package/src/menu/createMenuItem.ts +149 -0
- package/src/menu/createMenuTrigger.ts +88 -0
- package/src/menu/index.ts +18 -0
- package/src/meter/createMeter.ts +75 -0
- package/src/meter/index.ts +1 -0
- package/src/numberfield/createNumberField.ts +268 -0
- package/src/numberfield/index.ts +5 -0
- package/src/overlays/ariaHideOutside.ts +219 -0
- package/src/overlays/createInteractOutside.ts +149 -0
- package/src/overlays/createModal.tsx +202 -0
- package/src/overlays/createOverlay.ts +155 -0
- package/src/overlays/createOverlayTrigger.ts +85 -0
- package/src/overlays/createPreventScroll.ts +266 -0
- package/src/overlays/index.ts +44 -0
- package/src/popover/calculatePosition.ts +766 -0
- package/src/popover/createOverlayPosition.ts +356 -0
- package/src/popover/createPopover.ts +170 -0
- package/src/popover/index.ts +24 -0
- package/src/progress/createProgressBar.ts +128 -0
- package/src/progress/index.ts +5 -0
- package/src/radio/createRadio.ts +287 -0
- package/src/radio/createRadioGroup.ts +189 -0
- package/src/radio/createRadioGroupState.ts +201 -0
- package/src/radio/index.ts +23 -0
- package/src/searchfield/createSearchField.ts +186 -0
- package/src/searchfield/index.ts +2 -0
- package/src/select/createHiddenSelect.tsx +236 -0
- package/src/select/createSelect.ts +395 -0
- package/src/select/index.ts +14 -0
- package/src/selection/createTypeSelect.ts +201 -0
- package/src/selection/index.ts +6 -0
- package/src/separator/createSeparator.ts +82 -0
- package/src/separator/index.ts +6 -0
- package/src/slider/createSlider.ts +349 -0
- package/src/slider/index.ts +2 -0
- package/src/ssr/index.tsx +370 -0
- package/src/switch/createSwitch.ts +70 -0
- package/src/switch/index.ts +1 -0
- package/src/table/createTable.ts +526 -0
- package/src/table/createTableCell.ts +147 -0
- package/src/table/createTableColumnHeader.ts +115 -0
- package/src/table/createTableHeaderRow.ts +40 -0
- package/src/table/createTableRow.ts +155 -0
- package/src/table/createTableRowGroup.ts +32 -0
- package/src/table/createTableSelectAllCheckbox.ts +73 -0
- package/src/table/createTableSelectionCheckbox.ts +59 -0
- package/src/table/index.ts +30 -0
- package/src/table/types.ts +165 -0
- package/src/tabs/createTabs.ts +472 -0
- package/src/tabs/index.ts +14 -0
- package/src/tag/createTag.ts +194 -0
- package/src/tag/createTagGroup.ts +154 -0
- package/src/tag/index.ts +12 -0
- package/src/textfield/createTextField.ts +198 -0
- package/src/textfield/index.ts +5 -0
- package/src/toast/createToast.ts +118 -0
- package/src/toast/createToastRegion.ts +100 -0
- package/src/toast/index.ts +11 -0
- package/src/toggle/createToggle.ts +223 -0
- package/src/toggle/createToggleState.ts +94 -0
- package/src/toggle/index.ts +7 -0
- package/src/toolbar/createToolbar.ts +369 -0
- package/src/toolbar/index.ts +6 -0
- package/src/tooltip/createTooltip.ts +79 -0
- package/src/tooltip/createTooltipTrigger.ts +222 -0
- package/src/tooltip/index.ts +6 -0
- package/src/tree/createTree.ts +246 -0
- package/src/tree/createTreeItem.ts +233 -0
- package/src/tree/createTreeSelectionCheckbox.ts +68 -0
- package/src/tree/index.ts +16 -0
- package/src/tree/types.ts +87 -0
- package/src/utils/createDescription.ts +137 -0
- package/src/utils/dom.ts +327 -0
- package/src/utils/env.ts +54 -0
- package/src/utils/events.ts +106 -0
- package/src/utils/filterDOMProps.ts +116 -0
- package/src/utils/focus.ts +151 -0
- package/src/utils/geometry.ts +115 -0
- package/src/utils/globalListeners.ts +142 -0
- package/src/utils/index.ts +80 -0
- package/src/utils/mergeProps.ts +52 -0
- package/src/utils/platform.ts +52 -0
- package/src/utils/reactivity.ts +36 -0
- package/src/utils/textSelection.ts +114 -0
- package/src/visually-hidden/createVisuallyHidden.ts +124 -0
- package/src/visually-hidden/index.ts +6 -0
- package/dist/index.jsx +0 -15845
- package/dist/index.jsx.map +0 -7
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createFocusable - Makes an element focusable and capable of auto focus.
|
|
3
|
+
*
|
|
4
|
+
* This is a 1-1 port of React-Aria's useFocusable hook adapted for SolidJS.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { JSX, Accessor, createContext, useContext, onMount } from 'solid-js';
|
|
8
|
+
import { createFocus, type FocusEvents } from './createFocus';
|
|
9
|
+
import { createKeyboard, type KeyboardEvents } from './createKeyboard';
|
|
10
|
+
import { mergeProps, focusSafely } from '../utils';
|
|
11
|
+
|
|
12
|
+
export interface FocusableDOMProps {
|
|
13
|
+
/** Whether to exclude the element from the sequential tab order. */
|
|
14
|
+
excludeFromTabOrder?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface FocusableProps extends FocusEvents, KeyboardEvents {
|
|
18
|
+
/** Whether the element should receive focus on mount. */
|
|
19
|
+
autoFocus?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CreateFocusableProps extends FocusableProps, FocusableDOMProps {
|
|
23
|
+
/** Whether focus should be disabled. */
|
|
24
|
+
isDisabled?: Accessor<boolean> | boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface FocusableResult {
|
|
28
|
+
/** Props to spread on the focusable element. */
|
|
29
|
+
focusableProps: JSX.HTMLAttributes<HTMLElement>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- FocusableContext ---
|
|
33
|
+
|
|
34
|
+
export interface FocusableContextValue {
|
|
35
|
+
ref?: (el: HTMLElement) => void;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Context for passing focusable props to nested focusable children.
|
|
41
|
+
* Used by FocusableProvider to pass DOM props to the nearest focusable child.
|
|
42
|
+
*/
|
|
43
|
+
export const FocusableContext = createContext<FocusableContextValue | null>(null);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Hook to consume the FocusableContext and sync the ref.
|
|
47
|
+
*/
|
|
48
|
+
function useFocusableContext(
|
|
49
|
+
setRef: (el: HTMLElement) => void
|
|
50
|
+
): Omit<FocusableContextValue, 'ref'> {
|
|
51
|
+
const context = useContext(FocusableContext) || {};
|
|
52
|
+
|
|
53
|
+
// If context has a ref, sync our ref to it
|
|
54
|
+
if (context.ref) {
|
|
55
|
+
const contextRef = context.ref;
|
|
56
|
+
// Create a combined ref that calls both
|
|
57
|
+
const originalSetRef = setRef;
|
|
58
|
+
setRef = (el: HTMLElement) => {
|
|
59
|
+
originalSetRef(el);
|
|
60
|
+
contextRef(el);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Return context without the ref
|
|
65
|
+
const { ref: _, ...otherProps } = context;
|
|
66
|
+
return otherProps;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface FocusableProviderProps {
|
|
70
|
+
/** The child element to provide DOM props to. */
|
|
71
|
+
children?: JSX.Element;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isDisabledValue(isDisabled: Accessor<boolean> | boolean | undefined): boolean {
|
|
75
|
+
if (typeof isDisabled === 'function') {
|
|
76
|
+
return isDisabled();
|
|
77
|
+
}
|
|
78
|
+
return isDisabled ?? false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Makes an element focusable, handling disabled state and tab order.
|
|
83
|
+
* Provides focus state tracking and autoFocus support.
|
|
84
|
+
*
|
|
85
|
+
* Based on react-aria's useFocusable but adapted for SolidJS.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* import { createFocusable } from 'solidaria';
|
|
90
|
+
*
|
|
91
|
+
* function FocusableInput(props) {
|
|
92
|
+
* let ref;
|
|
93
|
+
* const { focusableProps } = createFocusable({
|
|
94
|
+
* autoFocus: props.autoFocus,
|
|
95
|
+
* onFocusChange: (focused) => console.log('Focus:', focused),
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* return (
|
|
99
|
+
* <input
|
|
100
|
+
* {...focusableProps}
|
|
101
|
+
* ref={(el) => { ref = el; focusableProps.ref?.(el); }}
|
|
102
|
+
* />
|
|
103
|
+
* );
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export function createFocusable(
|
|
108
|
+
props: CreateFocusableProps = {},
|
|
109
|
+
ref?: (el: HTMLElement) => void
|
|
110
|
+
): FocusableResult {
|
|
111
|
+
let elementRef: HTMLElement | null = null;
|
|
112
|
+
let autoFocusDone = false;
|
|
113
|
+
|
|
114
|
+
// Set up ref handler
|
|
115
|
+
const setRef = (el: HTMLElement) => {
|
|
116
|
+
elementRef = el;
|
|
117
|
+
ref?.(el);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Get focus and keyboard props from the respective hooks
|
|
121
|
+
const { focusProps } = createFocus({
|
|
122
|
+
isDisabled: isDisabledValue(props.isDisabled),
|
|
123
|
+
onFocus: props.onFocus,
|
|
124
|
+
onBlur: props.onBlur,
|
|
125
|
+
onFocusChange: props.onFocusChange,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const { keyboardProps } = createKeyboard({
|
|
129
|
+
isDisabled: isDisabledValue(props.isDisabled),
|
|
130
|
+
onKeyDown: props.onKeyDown,
|
|
131
|
+
onKeyUp: props.onKeyUp,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Merge focus and keyboard interactions
|
|
135
|
+
const interactions = mergeProps(focusProps, keyboardProps);
|
|
136
|
+
|
|
137
|
+
// Get context props (from FocusableProvider if present)
|
|
138
|
+
const contextProps = useFocusableContext(setRef);
|
|
139
|
+
const interactionProps = isDisabledValue(props.isDisabled) ? {} : contextProps;
|
|
140
|
+
|
|
141
|
+
// Handle autoFocus
|
|
142
|
+
onMount(() => {
|
|
143
|
+
if (props.autoFocus && elementRef && !autoFocusDone) {
|
|
144
|
+
focusSafely(elementRef);
|
|
145
|
+
autoFocusDone = true;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Always set a tabIndex so that Safari allows focusing native buttons and inputs.
|
|
150
|
+
let tabIndex: number | undefined = props.excludeFromTabOrder ? -1 : 0;
|
|
151
|
+
if (isDisabledValue(props.isDisabled)) {
|
|
152
|
+
tabIndex = undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Build final focusable props
|
|
156
|
+
const focusableProps = mergeProps(
|
|
157
|
+
{
|
|
158
|
+
...interactions,
|
|
159
|
+
tabIndex,
|
|
160
|
+
ref: setRef,
|
|
161
|
+
},
|
|
162
|
+
interactionProps
|
|
163
|
+
) as JSX.HTMLAttributes<HTMLElement>;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
focusableProps,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createHover hook for Solidaria
|
|
3
|
+
*
|
|
4
|
+
* Handles pointer hover interactions for an element. Normalizes behavior
|
|
5
|
+
* across browsers and platforms, and ignores emulated mouse events on touch devices.
|
|
6
|
+
*
|
|
7
|
+
* Port of @react-aria/interactions useHover.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { type JSX, type Accessor, createSignal, createEffect, onCleanup, createMemo } from 'solid-js';
|
|
11
|
+
import { type MaybeAccessor, access } from '../utils/reactivity';
|
|
12
|
+
import { isTestEnv } from '../utils/env';
|
|
13
|
+
import { createGlobalListeners, nodeContains } from '../utils';
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// TYPES
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
export interface HoverEvent {
|
|
20
|
+
/** The type of hover event being fired. */
|
|
21
|
+
type: 'hoverstart' | 'hoverend';
|
|
22
|
+
/** The pointer type that triggered the hover event. */
|
|
23
|
+
pointerType: 'mouse' | 'pen';
|
|
24
|
+
/** The target element of the hover event. */
|
|
25
|
+
target: Element;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface HoverEvents {
|
|
29
|
+
/** Handler called when the hover starts. */
|
|
30
|
+
onHoverStart?: (e: HoverEvent) => void;
|
|
31
|
+
/** Handler called when the hover ends. */
|
|
32
|
+
onHoverEnd?: (e: HoverEvent) => void;
|
|
33
|
+
/** Handler called when the hover state changes. */
|
|
34
|
+
onHoverChange?: (isHovering: boolean) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CreateHoverProps extends HoverEvents {
|
|
38
|
+
/** Whether the hover events should be disabled. */
|
|
39
|
+
isDisabled?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Event handler props returned by createHover - safe to spread on any element */
|
|
43
|
+
export type HoverProps = Pick<
|
|
44
|
+
JSX.HTMLAttributes<HTMLElement>,
|
|
45
|
+
| 'onPointerEnter'
|
|
46
|
+
| 'onPointerLeave'
|
|
47
|
+
| 'onPointerOver'
|
|
48
|
+
| 'onPointerOut'
|
|
49
|
+
| 'onMouseEnter'
|
|
50
|
+
| 'onMouseLeave'
|
|
51
|
+
>;
|
|
52
|
+
|
|
53
|
+
export interface HoverResult {
|
|
54
|
+
/** Props to spread on the target element. */
|
|
55
|
+
hoverProps: HoverProps;
|
|
56
|
+
/** Whether the element is currently hovered. */
|
|
57
|
+
isHovered: Accessor<boolean>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================
|
|
61
|
+
// GLOBAL STATE
|
|
62
|
+
// ============================================
|
|
63
|
+
|
|
64
|
+
// iOS fires onPointerEnter twice: once with pointerType="touch" and again with pointerType="mouse".
|
|
65
|
+
// We want to ignore these emulated events so they do not trigger hover behavior.
|
|
66
|
+
let globalIgnoreEmulatedMouseEvents = false;
|
|
67
|
+
let hoverCount = 0;
|
|
68
|
+
|
|
69
|
+
function setGlobalIgnoreEmulatedMouseEvents() {
|
|
70
|
+
globalIgnoreEmulatedMouseEvents = true;
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
globalIgnoreEmulatedMouseEvents = false;
|
|
73
|
+
}, 50);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function handleGlobalPointerEvent(e: PointerEvent) {
|
|
77
|
+
if (e.pointerType === 'touch') {
|
|
78
|
+
setGlobalIgnoreEmulatedMouseEvents();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function setupGlobalTouchEvents() {
|
|
83
|
+
if (typeof document === 'undefined') {
|
|
84
|
+
return () => {};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (hoverCount === 0) {
|
|
88
|
+
if (typeof PointerEvent !== 'undefined') {
|
|
89
|
+
document.addEventListener('pointerup', handleGlobalPointerEvent);
|
|
90
|
+
} else if (isTestEnv()) {
|
|
91
|
+
document.addEventListener('touchend', setGlobalIgnoreEmulatedMouseEvents);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
hoverCount++;
|
|
96
|
+
return () => {
|
|
97
|
+
hoverCount--;
|
|
98
|
+
if (hoverCount > 0) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (typeof PointerEvent !== 'undefined') {
|
|
103
|
+
document.removeEventListener('pointerup', handleGlobalPointerEvent);
|
|
104
|
+
} else if (isTestEnv()) {
|
|
105
|
+
document.removeEventListener('touchend', setGlobalIgnoreEmulatedMouseEvents);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================
|
|
111
|
+
// IMPLEMENTATION
|
|
112
|
+
// ============================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Handles pointer hover interactions for an element.
|
|
116
|
+
*/
|
|
117
|
+
export function createHover(props: MaybeAccessor<CreateHoverProps> = {}): HoverResult {
|
|
118
|
+
const getProps = () => access(props);
|
|
119
|
+
const [isHovered, setIsHovered] = createSignal(false);
|
|
120
|
+
const { addGlobalListener, removeAllGlobalListeners } = createGlobalListeners();
|
|
121
|
+
|
|
122
|
+
// Track internal hover state
|
|
123
|
+
let state = {
|
|
124
|
+
isHovered: false,
|
|
125
|
+
ignoreEmulatedMouseEvents: false,
|
|
126
|
+
pointerType: '' as 'mouse' | 'pen' | '',
|
|
127
|
+
target: null as Element | null,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Setup global touch events
|
|
131
|
+
createEffect(() => {
|
|
132
|
+
const cleanup = setupGlobalTouchEvents();
|
|
133
|
+
onCleanup(cleanup);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Reset hover when disabled
|
|
137
|
+
createEffect(() => {
|
|
138
|
+
const p = getProps();
|
|
139
|
+
if (p.isDisabled && state.isHovered) {
|
|
140
|
+
triggerHoverEnd(state.target as Element, state.pointerType as 'mouse' | 'pen');
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
function triggerHoverStart(target: Element, pointerType: 'mouse' | 'pen', eventTarget: Element | null) {
|
|
145
|
+
const p = getProps();
|
|
146
|
+
state.pointerType = pointerType;
|
|
147
|
+
|
|
148
|
+
const isOverTarget = eventTarget instanceof Element ? target.contains(eventTarget) : true;
|
|
149
|
+
if (p.isDisabled || state.isHovered || !isOverTarget) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
state.isHovered = true;
|
|
154
|
+
state.target = target;
|
|
155
|
+
|
|
156
|
+
addGlobalListener('pointerover', (event: PointerEvent) => {
|
|
157
|
+
if (state.isHovered && state.target && !nodeContains(state.target, event.target as Element)) {
|
|
158
|
+
triggerHoverEnd(event.target as Element, event.pointerType as 'mouse' | 'pen');
|
|
159
|
+
}
|
|
160
|
+
}, { capture: true });
|
|
161
|
+
|
|
162
|
+
p.onHoverStart?.({
|
|
163
|
+
type: 'hoverstart',
|
|
164
|
+
target,
|
|
165
|
+
pointerType,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
p.onHoverChange?.(true);
|
|
169
|
+
setIsHovered(true);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function triggerHoverEnd(target: Element | null, pointerType: 'mouse' | 'pen') {
|
|
173
|
+
const p = getProps();
|
|
174
|
+
state.pointerType = '';
|
|
175
|
+
state.target = null;
|
|
176
|
+
|
|
177
|
+
if (!state.isHovered || !target) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
state.isHovered = false;
|
|
182
|
+
removeAllGlobalListeners();
|
|
183
|
+
|
|
184
|
+
p.onHoverEnd?.({
|
|
185
|
+
type: 'hoverend',
|
|
186
|
+
target,
|
|
187
|
+
pointerType,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
p.onHoverChange?.(false);
|
|
191
|
+
setIsHovered(false);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const hoverProps = createMemo<JSX.HTMLAttributes<HTMLElement>>(() => {
|
|
195
|
+
if (typeof PointerEvent !== 'undefined') {
|
|
196
|
+
return {
|
|
197
|
+
onPointerEnter: (e: PointerEvent) => {
|
|
198
|
+
if (globalIgnoreEmulatedMouseEvents && e.pointerType === 'mouse') {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (e.pointerType === 'touch') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
triggerHoverStart(e.currentTarget as Element, e.pointerType as 'mouse' | 'pen', e.target as Element);
|
|
205
|
+
},
|
|
206
|
+
onPointerLeave: (e: PointerEvent) => {
|
|
207
|
+
const p = getProps();
|
|
208
|
+
if (!p.isDisabled && (e.currentTarget as Element).contains(e.target as Element)) {
|
|
209
|
+
triggerHoverEnd(e.currentTarget as Element, e.pointerType as 'mouse' | 'pen');
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
onPointerOver: (e: PointerEvent) => {
|
|
213
|
+
if (globalIgnoreEmulatedMouseEvents && e.pointerType === 'mouse') {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (e.pointerType === 'touch') {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
triggerHoverStart(e.currentTarget as Element, e.pointerType as 'mouse' | 'pen', e.target as Element);
|
|
220
|
+
},
|
|
221
|
+
onPointerOut: (e: PointerEvent) => {
|
|
222
|
+
const p = getProps();
|
|
223
|
+
if (!p.isDisabled && (e.currentTarget as Element).contains(e.target as Element)) {
|
|
224
|
+
triggerHoverEnd(e.currentTarget as Element, e.pointerType as 'mouse' | 'pen');
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Fallback for environments without PointerEvent (mainly tests)
|
|
231
|
+
return {
|
|
232
|
+
onTouchStart: () => {
|
|
233
|
+
state.ignoreEmulatedMouseEvents = true;
|
|
234
|
+
},
|
|
235
|
+
onMouseEnter: (e: MouseEvent) => {
|
|
236
|
+
if (!state.ignoreEmulatedMouseEvents && !globalIgnoreEmulatedMouseEvents) {
|
|
237
|
+
triggerHoverStart(e.currentTarget as Element, 'mouse', e.target as Element);
|
|
238
|
+
}
|
|
239
|
+
state.ignoreEmulatedMouseEvents = false;
|
|
240
|
+
},
|
|
241
|
+
onMouseLeave: (e: MouseEvent) => {
|
|
242
|
+
const p = getProps();
|
|
243
|
+
if (!p.isDisabled && (e.currentTarget as Element).contains(e.target as Element)) {
|
|
244
|
+
triggerHoverEnd(e.currentTarget as Element, 'mouse');
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
hoverProps: hoverProps() as HoverProps,
|
|
252
|
+
isHovered,
|
|
253
|
+
};
|
|
254
|
+
}
|