@proyecto-viviana/solidaria 0.0.1 → 0.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.
Files changed (56) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/index.js.map +1 -1
  3. package/package.json +4 -3
  4. package/src/button/createButton.ts +135 -0
  5. package/src/button/createToggleButton.ts +101 -0
  6. package/src/button/index.ts +4 -0
  7. package/src/button/types.ts +67 -0
  8. package/src/checkbox/createCheckbox.ts +135 -0
  9. package/src/checkbox/createCheckboxGroup.ts +137 -0
  10. package/src/checkbox/createCheckboxGroupItem.ts +117 -0
  11. package/src/checkbox/createCheckboxGroupState.ts +193 -0
  12. package/src/checkbox/index.ts +13 -0
  13. package/src/index.ts +128 -0
  14. package/src/interactions/FocusableProvider.tsx +44 -0
  15. package/src/interactions/PressEvent.ts +112 -0
  16. package/src/interactions/createFocus.ts +157 -0
  17. package/src/interactions/createFocusRing.ts +142 -0
  18. package/src/interactions/createFocusWithin.ts +141 -0
  19. package/src/interactions/createFocusable.ts +168 -0
  20. package/src/interactions/createHover.ts +214 -0
  21. package/src/interactions/createKeyboard.ts +82 -0
  22. package/src/interactions/createPress.ts +758 -0
  23. package/src/interactions/index.ts +45 -0
  24. package/src/label/createField.ts +145 -0
  25. package/src/label/createLabel.ts +116 -0
  26. package/src/label/createLabels.ts +50 -0
  27. package/src/label/index.ts +19 -0
  28. package/src/link/createLink.ts +176 -0
  29. package/src/link/index.ts +1 -0
  30. package/src/progress/createProgressBar.ts +128 -0
  31. package/src/progress/index.ts +5 -0
  32. package/src/radio/createRadio.ts +286 -0
  33. package/src/radio/createRadioGroup.ts +189 -0
  34. package/src/radio/createRadioGroupState.ts +201 -0
  35. package/src/radio/index.ts +23 -0
  36. package/src/separator/createSeparator.ts +82 -0
  37. package/src/separator/index.ts +6 -0
  38. package/src/ssr/index.ts +36 -0
  39. package/src/switch/createSwitch.ts +70 -0
  40. package/src/switch/index.ts +1 -0
  41. package/src/textfield/createTextField.ts +198 -0
  42. package/src/textfield/index.ts +5 -0
  43. package/src/toggle/createToggle.ts +222 -0
  44. package/src/toggle/createToggleState.ts +94 -0
  45. package/src/toggle/index.ts +7 -0
  46. package/src/utils/dom.ts +244 -0
  47. package/src/utils/events.ts +119 -0
  48. package/src/utils/filterDOMProps.ts +116 -0
  49. package/src/utils/focus.ts +151 -0
  50. package/src/utils/geometry.ts +115 -0
  51. package/src/utils/globalListeners.ts +142 -0
  52. package/src/utils/index.ts +66 -0
  53. package/src/utils/mergeProps.ts +49 -0
  54. package/src/utils/platform.ts +52 -0
  55. package/src/utils/reactivity.ts +36 -0
  56. package/src/utils/textSelection.ts +114 -0
@@ -0,0 +1,214 @@
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
+
13
+ // ============================================
14
+ // TYPES
15
+ // ============================================
16
+
17
+ export interface HoverEvent {
18
+ /** The type of hover event being fired. */
19
+ type: 'hoverstart' | 'hoverend';
20
+ /** The pointer type that triggered the hover event. */
21
+ pointerType: 'mouse' | 'pen';
22
+ /** The target element of the hover event. */
23
+ target: Element;
24
+ }
25
+
26
+ export interface HoverEvents {
27
+ /** Handler called when the hover starts. */
28
+ onHoverStart?: (e: HoverEvent) => void;
29
+ /** Handler called when the hover ends. */
30
+ onHoverEnd?: (e: HoverEvent) => void;
31
+ /** Handler called when the hover state changes. */
32
+ onHoverChange?: (isHovering: boolean) => void;
33
+ }
34
+
35
+ export interface CreateHoverProps extends HoverEvents {
36
+ /** Whether the hover events should be disabled. */
37
+ isDisabled?: boolean;
38
+ }
39
+
40
+ export interface HoverResult {
41
+ /** Props to spread on the target element. */
42
+ hoverProps: JSX.HTMLAttributes<HTMLElement>;
43
+ /** Whether the element is currently hovered. */
44
+ isHovered: Accessor<boolean>;
45
+ }
46
+
47
+ // ============================================
48
+ // GLOBAL STATE
49
+ // ============================================
50
+
51
+ // iOS fires onPointerEnter twice: once with pointerType="touch" and again with pointerType="mouse".
52
+ // We want to ignore these emulated events so they do not trigger hover behavior.
53
+ let globalIgnoreEmulatedMouseEvents = false;
54
+ let hoverCount = 0;
55
+
56
+ function setGlobalIgnoreEmulatedMouseEvents() {
57
+ globalIgnoreEmulatedMouseEvents = true;
58
+ setTimeout(() => {
59
+ globalIgnoreEmulatedMouseEvents = false;
60
+ }, 50);
61
+ }
62
+
63
+ function handleGlobalPointerEvent(e: PointerEvent) {
64
+ if (e.pointerType === 'touch') {
65
+ setGlobalIgnoreEmulatedMouseEvents();
66
+ }
67
+ }
68
+
69
+ function setupGlobalTouchEvents() {
70
+ if (typeof document === 'undefined') {
71
+ return () => {};
72
+ }
73
+
74
+ if (hoverCount === 0) {
75
+ if (typeof PointerEvent !== 'undefined') {
76
+ document.addEventListener('pointerup', handleGlobalPointerEvent);
77
+ } else if (process.env.NODE_ENV === 'test') {
78
+ document.addEventListener('touchend', setGlobalIgnoreEmulatedMouseEvents);
79
+ }
80
+ }
81
+
82
+ hoverCount++;
83
+ return () => {
84
+ hoverCount--;
85
+ if (hoverCount > 0) {
86
+ return;
87
+ }
88
+
89
+ if (typeof PointerEvent !== 'undefined') {
90
+ document.removeEventListener('pointerup', handleGlobalPointerEvent);
91
+ } else if (process.env.NODE_ENV === 'test') {
92
+ document.removeEventListener('touchend', setGlobalIgnoreEmulatedMouseEvents);
93
+ }
94
+ };
95
+ }
96
+
97
+ // ============================================
98
+ // IMPLEMENTATION
99
+ // ============================================
100
+
101
+ /**
102
+ * Handles pointer hover interactions for an element.
103
+ */
104
+ export function createHover(props: MaybeAccessor<CreateHoverProps> = {}): HoverResult {
105
+ const getProps = () => access(props);
106
+ const [isHovered, setIsHovered] = createSignal(false);
107
+
108
+ // Track internal hover state
109
+ let state = {
110
+ isHovered: false,
111
+ ignoreEmulatedMouseEvents: false,
112
+ pointerType: '' as 'mouse' | 'pen' | '',
113
+ target: null as Element | null,
114
+ };
115
+
116
+ // Setup global touch events
117
+ createEffect(() => {
118
+ const cleanup = setupGlobalTouchEvents();
119
+ onCleanup(cleanup);
120
+ });
121
+
122
+ // Reset hover when disabled
123
+ createEffect(() => {
124
+ const p = getProps();
125
+ if (p.isDisabled && state.isHovered) {
126
+ triggerHoverEnd(state.target as Element, state.pointerType as 'mouse' | 'pen');
127
+ }
128
+ });
129
+
130
+ function triggerHoverStart(target: Element, pointerType: 'mouse' | 'pen') {
131
+ const p = getProps();
132
+ state.pointerType = pointerType;
133
+
134
+ if (p.isDisabled || state.isHovered) {
135
+ return;
136
+ }
137
+
138
+ state.isHovered = true;
139
+ state.target = target;
140
+
141
+ p.onHoverStart?.({
142
+ type: 'hoverstart',
143
+ target,
144
+ pointerType,
145
+ });
146
+
147
+ p.onHoverChange?.(true);
148
+ setIsHovered(true);
149
+ }
150
+
151
+ function triggerHoverEnd(target: Element | null, pointerType: 'mouse' | 'pen') {
152
+ const p = getProps();
153
+ state.pointerType = '';
154
+ state.target = null;
155
+
156
+ if (!state.isHovered || !target) {
157
+ return;
158
+ }
159
+
160
+ state.isHovered = false;
161
+
162
+ p.onHoverEnd?.({
163
+ type: 'hoverend',
164
+ target,
165
+ pointerType,
166
+ });
167
+
168
+ p.onHoverChange?.(false);
169
+ setIsHovered(false);
170
+ }
171
+
172
+ const hoverProps = createMemo<JSX.HTMLAttributes<HTMLElement>>(() => {
173
+ if (typeof PointerEvent !== 'undefined') {
174
+ return {
175
+ onPointerEnter: (e: PointerEvent) => {
176
+ if (globalIgnoreEmulatedMouseEvents && e.pointerType === 'mouse') {
177
+ return;
178
+ }
179
+ if (e.pointerType === 'touch') {
180
+ return;
181
+ }
182
+ triggerHoverStart(e.currentTarget as Element, e.pointerType as 'mouse' | 'pen');
183
+ },
184
+ onPointerLeave: (e: PointerEvent) => {
185
+ const p = getProps();
186
+ if (!p.isDisabled && (e.currentTarget as Element).contains(e.target as Element)) {
187
+ triggerHoverEnd(e.currentTarget as Element, e.pointerType as 'mouse' | 'pen');
188
+ }
189
+ },
190
+ };
191
+ }
192
+
193
+ // Fallback for environments without PointerEvent (mainly tests)
194
+ return {
195
+ onMouseEnter: (e: MouseEvent) => {
196
+ if (!state.ignoreEmulatedMouseEvents && !globalIgnoreEmulatedMouseEvents) {
197
+ triggerHoverStart(e.currentTarget as Element, 'mouse');
198
+ }
199
+ state.ignoreEmulatedMouseEvents = false;
200
+ },
201
+ onMouseLeave: (e: MouseEvent) => {
202
+ const p = getProps();
203
+ if (!p.isDisabled && (e.currentTarget as Element).contains(e.target as Element)) {
204
+ triggerHoverEnd(e.currentTarget as Element, 'mouse');
205
+ }
206
+ },
207
+ };
208
+ });
209
+
210
+ return {
211
+ hoverProps: hoverProps() as JSX.HTMLAttributes<HTMLElement>,
212
+ isHovered,
213
+ };
214
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * createKeyboard - Handles keyboard interactions for a focusable element.
3
+ *
4
+ * This is a 1-1 port of React-Aria's useKeyboard hook adapted for SolidJS.
5
+ */
6
+
7
+ import { JSX } from 'solid-js';
8
+
9
+ /**
10
+ * Keyboard event with continuePropagation support.
11
+ * By default, keyboard events stop propagation.
12
+ */
13
+ export interface KeyboardEvent extends globalThis.KeyboardEvent {
14
+ /** Call this to allow the event to propagate to parent elements. */
15
+ continuePropagation(): void;
16
+ }
17
+
18
+ export interface KeyboardEvents {
19
+ /** Handler that is called when a key is pressed. */
20
+ onKeyDown?: (e: KeyboardEvent) => void;
21
+ /** Handler that is called when a key is released. */
22
+ onKeyUp?: (e: KeyboardEvent) => void;
23
+ }
24
+
25
+ export interface CreateKeyboardProps extends KeyboardEvents {
26
+ /** Whether the keyboard events should be disabled. */
27
+ isDisabled?: boolean;
28
+ }
29
+
30
+ export interface KeyboardResult {
31
+ /** Props to spread onto the target element. */
32
+ keyboardProps: JSX.HTMLAttributes<HTMLElement>;
33
+ }
34
+
35
+ /**
36
+ * Wraps a keyboard event handler to make stopPropagation the default,
37
+ * and support continuePropagation instead.
38
+ */
39
+ function createEventHandler<T extends globalThis.KeyboardEvent>(
40
+ handler?: (e: KeyboardEvent) => void
41
+ ): ((e: T) => void) | undefined {
42
+ if (!handler) {
43
+ return undefined;
44
+ }
45
+
46
+ return (e: T) => {
47
+ let shouldStopPropagation = true;
48
+
49
+ // Create a wrapped event with continuePropagation
50
+ const event = Object.assign(e, {
51
+ continuePropagation() {
52
+ shouldStopPropagation = false;
53
+ },
54
+ }) as KeyboardEvent;
55
+
56
+ handler(event);
57
+
58
+ if (shouldStopPropagation) {
59
+ e.stopPropagation();
60
+ }
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Handles keyboard interactions for a focusable element.
66
+ *
67
+ * Based on react-aria's useKeyboard but adapted for SolidJS.
68
+ */
69
+ export function createKeyboard(props: CreateKeyboardProps = {}): KeyboardResult {
70
+ if (props.isDisabled) {
71
+ return {
72
+ keyboardProps: {},
73
+ };
74
+ }
75
+
76
+ return {
77
+ keyboardProps: {
78
+ onKeyDown: createEventHandler(props.onKeyDown),
79
+ onKeyUp: createEventHandler(props.onKeyUp),
80
+ },
81
+ };
82
+ }