@luna_ui/luna 0.11.0 → 0.20.0
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/api-DAWeanTX.js +1 -0
- package/dist/api-qXll116-.d.ts +80 -0
- package/dist/cli.mjs +27 -22
- package/dist/css/index.js +1 -0
- package/dist/event-utils.d.ts +1 -1
- package/dist/event-utils.js +1 -1
- package/dist/{index-BZoM-af5.d.ts → index-VY8G32hr.d.ts} +16 -76
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/raw.d.ts +2 -0
- package/dist/raw.js +1 -0
- package/dist/resource.d.ts +41 -0
- package/dist/resource.js +1 -0
- package/dist/router-lite.d.ts +44 -0
- package/dist/router-lite.js +1 -0
- package/dist/signals-shared.d.ts +12 -0
- package/dist/signals-shared.js +1 -0
- package/dist/signals.d.ts +2 -3
- package/dist/signals.js +1 -1
- package/dist/vite-plugin.d.ts +7708 -2
- package/dist/vite-plugin.js +7 -6
- package/package.json +30 -11
- package/dist/event-utils-9cHYnvun.js +0 -1
- package/dist/src-BFWjzzPo.js +0 -1
- package/src/css/extract.ts +0 -798
- package/src/css/index.ts +0 -10
- package/src/css/inject.ts +0 -205
- package/src/css/inline.ts +0 -182
- package/src/css/minify.ts +0 -70
- package/src/css/optimizer.ts +0 -6
- package/src/css/runtime.ts +0 -344
- package/src/css-optimizer/README.md +0 -353
- package/src/css-optimizer/cooccurrence.ts +0 -100
- package/src/css-optimizer/core.ts +0 -263
- package/src/css-optimizer/extractors.ts +0 -243
- package/src/css-optimizer/hash.ts +0 -54
- package/src/css-optimizer/index.ts +0 -129
- package/src/css-optimizer/merge.ts +0 -109
- package/src/css-optimizer/moonbit-analyzer.ts +0 -210
- package/src/css-optimizer/parser.ts +0 -120
- package/src/css-optimizer/pattern.ts +0 -171
- package/src/css-optimizer/transformers.ts +0 -301
- package/src/css-optimizer/types.ts +0 -128
- package/src/event-utils.ts +0 -227
- package/src/hydration/createHydrator.ts +0 -62
- package/src/hydration/delegate.ts +0 -62
- package/src/hydration/drag.ts +0 -214
- package/src/hydration/index.ts +0 -12
- package/src/hydration/keyboard.ts +0 -64
- package/src/hydration/toggle.ts +0 -101
- package/src/index.ts +0 -908
- package/src/jsx-dev-runtime.ts +0 -2
- package/src/jsx-runtime.ts +0 -398
- package/src/signals.ts +0 -113
- package/src/vite-plugin.ts +0 -718
- package/tests/__screenshots__/apg.test.ts/APG-Components---Accessibility-Tests-Button-Pattern-disabled-button-has-aria-disabled-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--createResource-error-is-undefined-when-pending-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--createResource-transitions-to-success-on-resolve-1.png +0 -0
- package/tests/apg.test.ts +0 -466
- package/tests/context.test.ts +0 -118
- package/tests/css-optimizer-extractors.test.ts +0 -264
- package/tests/css-optimizer-integration.test.ts +0 -566
- package/tests/css-optimizer-transformers.test.ts +0 -301
- package/tests/css-optimizer.test.ts +0 -646
- package/tests/css-runtime.bench.ts +0 -442
- package/tests/css-runtime.test.ts +0 -342
- package/tests/debounced.test.ts +0 -165
- package/tests/dom.test.ts +0 -873
- package/tests/integration.test.ts +0 -405
- package/tests/issue-11-show-null-to-truthy.test.ts +0 -176
- package/tests/issue-5-for-infinite-loop.test.ts +0 -516
- package/tests/jsx-runtime.test.tsx +0 -393
- package/tests/lifecycle.test.ts +0 -833
- package/tests/move-before.bench.ts +0 -304
- package/tests/preact-signals-comparison.test.ts +0 -1608
- package/tests/resource.test.ts +0 -170
- package/tests/router.test.ts +0 -117
- package/tests/show-initial-mount-leak.test.tsx +0 -182
- package/tests/solidjs-api.test.ts +0 -660
- package/tests/static-perf.bench.ts +0 -64
- package/tests/store.test.ts +0 -263
- package/tests/tsx-syntax.test.tsx +0 -404
- /package/dist/{event-utils-BkTM7rk5.d.ts → event-utils-BvAf0NwN.d.ts} +0 -0
package/src/event-utils.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Event utilities for common event handling patterns
|
|
3
|
-
*
|
|
4
|
-
* These utilities are tree-shakeable - only import what you use.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* import { getTargetValue, getDataId, stopEvent } from "@luna_ui/luna/event-utils";
|
|
8
|
-
*
|
|
9
|
-
* on=events().input(fn(e) {
|
|
10
|
-
* const value = getTargetValue(e);
|
|
11
|
-
* setValue(value);
|
|
12
|
-
* })
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Get value from input/textarea/select element
|
|
17
|
-
*/
|
|
18
|
-
export function getTargetValue(e: Event): string {
|
|
19
|
-
return (e.target as HTMLInputElement)?.value ?? "";
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Get checked state from checkbox/radio input
|
|
24
|
-
*/
|
|
25
|
-
export function getTargetChecked(e: Event): boolean {
|
|
26
|
-
return (e.target as HTMLInputElement)?.checked ?? false;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get selected index from select element
|
|
31
|
-
*/
|
|
32
|
-
export function getSelectedIndex(e: Event): number {
|
|
33
|
-
return (e.target as HTMLSelectElement)?.selectedIndex ?? -1;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Get client position (relative to viewport) from mouse/pointer/touch event
|
|
38
|
-
*/
|
|
39
|
-
export function getClientPos(e: MouseEvent | PointerEvent | TouchEvent): { x: number; y: number } {
|
|
40
|
-
if ("touches" in e && e.touches.length > 0) {
|
|
41
|
-
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
42
|
-
}
|
|
43
|
-
const me = e as MouseEvent;
|
|
44
|
-
return { x: me.clientX, y: me.clientY };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get offset position (relative to target element) from mouse/pointer event
|
|
49
|
-
*/
|
|
50
|
-
export function getOffsetPos(e: MouseEvent | PointerEvent): { x: number; y: number } {
|
|
51
|
-
return { x: e.offsetX, y: e.offsetY };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Get page position (relative to document) from mouse/pointer/touch event
|
|
56
|
-
*/
|
|
57
|
-
export function getPagePos(e: MouseEvent | PointerEvent | TouchEvent): { x: number; y: number } {
|
|
58
|
-
if ("touches" in e && e.touches.length > 0) {
|
|
59
|
-
return { x: e.touches[0].pageX, y: e.touches[0].pageY };
|
|
60
|
-
}
|
|
61
|
-
const me = e as MouseEvent;
|
|
62
|
-
return { x: me.pageX, y: me.pageY };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get movement delta from mouse/pointer event
|
|
67
|
-
*/
|
|
68
|
-
export function getMovement(e: MouseEvent | PointerEvent): { dx: number; dy: number } {
|
|
69
|
-
return { dx: e.movementX, dy: e.movementY };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get data attribute from event target or closest ancestor
|
|
74
|
-
*/
|
|
75
|
-
export function getDataAttr(e: Event, key: string): string | null {
|
|
76
|
-
const target = e.target as HTMLElement;
|
|
77
|
-
return target?.closest(`[data-${key}]`)?.getAttribute(`data-${key}`) ?? null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get data-id from closest ancestor (common pattern for list items)
|
|
82
|
-
*/
|
|
83
|
-
export function getDataId(e: Event): string | null {
|
|
84
|
-
return getDataAttr(e, "id");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Get all data attributes from event target as object
|
|
89
|
-
*/
|
|
90
|
-
export function getDataset(e: Event): DOMStringMap {
|
|
91
|
-
return (e.target as HTMLElement)?.dataset ?? {};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Check if event target matches a CSS selector
|
|
96
|
-
*/
|
|
97
|
-
export function matchesSelector(e: Event, selector: string): boolean {
|
|
98
|
-
const target = e.target as HTMLElement;
|
|
99
|
-
return target?.matches?.(selector) ?? false;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get closest ancestor matching selector from event target
|
|
104
|
-
*/
|
|
105
|
-
export function getClosest<T extends Element = HTMLElement>(e: Event, selector: string): T | null {
|
|
106
|
-
const target = e.target as HTMLElement;
|
|
107
|
-
return target?.closest?.(selector) as T | null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Prevent default behavior
|
|
112
|
-
*/
|
|
113
|
-
export function preventDefault(e: Event): void {
|
|
114
|
-
e.preventDefault();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Stop event propagation
|
|
119
|
-
*/
|
|
120
|
-
export function stopPropagation(e: Event): void {
|
|
121
|
-
e.stopPropagation();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Prevent default and stop propagation (convenience)
|
|
126
|
-
*/
|
|
127
|
-
export function stopEvent(e: Event): void {
|
|
128
|
-
e.preventDefault();
|
|
129
|
-
e.stopPropagation();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Check if IME is composing (for input/keyboard events)
|
|
134
|
-
* Use this to avoid handling partial IME input
|
|
135
|
-
*/
|
|
136
|
-
export function isComposing(e: Event): boolean {
|
|
137
|
-
return (e as InputEvent | KeyboardEvent).isComposing ?? false;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Check if modifier key is pressed
|
|
142
|
-
*/
|
|
143
|
-
export function hasModifier(e: KeyboardEvent | MouseEvent, modifier: "ctrl" | "alt" | "shift" | "meta"): boolean {
|
|
144
|
-
switch (modifier) {
|
|
145
|
-
case "ctrl": return e.ctrlKey;
|
|
146
|
-
case "alt": return e.altKey;
|
|
147
|
-
case "shift": return e.shiftKey;
|
|
148
|
-
case "meta": return e.metaKey;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Check if Cmd (Mac) or Ctrl (Windows/Linux) is pressed
|
|
154
|
-
*/
|
|
155
|
-
export function hasCmdOrCtrl(e: KeyboardEvent | MouseEvent): boolean {
|
|
156
|
-
// Mac uses metaKey (Cmd), others use ctrlKey
|
|
157
|
-
return e.metaKey || e.ctrlKey;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Get keyboard key with normalized names
|
|
162
|
-
*/
|
|
163
|
-
export function getKey(e: KeyboardEvent): string {
|
|
164
|
-
return e.key;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Check if Enter key was pressed (outside IME composition)
|
|
169
|
-
*/
|
|
170
|
-
export function isEnterKey(e: KeyboardEvent): boolean {
|
|
171
|
-
return e.key === "Enter" && !isComposing(e);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Check if Escape key was pressed
|
|
176
|
-
*/
|
|
177
|
-
export function isEscapeKey(e: KeyboardEvent): boolean {
|
|
178
|
-
return e.key === "Escape";
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Get wheel delta (normalized across browsers)
|
|
183
|
-
*/
|
|
184
|
-
export function getWheelDelta(e: WheelEvent): { deltaX: number; deltaY: number; deltaZ: number } {
|
|
185
|
-
return {
|
|
186
|
-
deltaX: e.deltaX,
|
|
187
|
-
deltaY: e.deltaY,
|
|
188
|
-
deltaZ: e.deltaZ,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get which mouse button was pressed
|
|
194
|
-
* 0 = primary (left), 1 = auxiliary (middle), 2 = secondary (right)
|
|
195
|
-
*/
|
|
196
|
-
export function getButton(e: MouseEvent): number {
|
|
197
|
-
return e.button;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Check if primary (left) mouse button was pressed
|
|
202
|
-
*/
|
|
203
|
-
export function isPrimaryButton(e: MouseEvent): boolean {
|
|
204
|
-
return e.button === 0;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Check if secondary (right) mouse button was pressed
|
|
209
|
-
*/
|
|
210
|
-
export function isSecondaryButton(e: MouseEvent): boolean {
|
|
211
|
-
return e.button === 2;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Get files from drag event
|
|
216
|
-
*/
|
|
217
|
-
export function getDroppedFiles(e: DragEvent): FileList | null {
|
|
218
|
-
return e.dataTransfer?.files ?? null;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get text data from drag/clipboard event
|
|
223
|
-
*/
|
|
224
|
-
export function getTextData(e: DragEvent | ClipboardEvent): string {
|
|
225
|
-
const dt = "dataTransfer" in e ? e.dataTransfer : e.clipboardData;
|
|
226
|
-
return dt?.getData("text/plain") ?? "";
|
|
227
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createHydrator - Wraps hydration logic with automatic guard and cleanup
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - Prevents double hydration via data-hydrated check
|
|
6
|
-
* - Automatically sets data-hydrated="true" on completion
|
|
7
|
-
* - Optional cleanup function support
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export type HydrateFn = (
|
|
11
|
-
element: Element,
|
|
12
|
-
state: unknown,
|
|
13
|
-
name: string
|
|
14
|
-
) => void | (() => void);
|
|
15
|
-
|
|
16
|
-
export type CleanupFn = () => void;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Create a hydration function with automatic guards
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* export const hydrate = createHydrator((el, state) => {
|
|
24
|
-
* // Your hydration logic here
|
|
25
|
-
* el.querySelector('button').onclick = () => { ... };
|
|
26
|
-
*
|
|
27
|
-
* // Optional: return cleanup function
|
|
28
|
-
* return () => { ... };
|
|
29
|
-
* });
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export function createHydrator(
|
|
33
|
-
fn: (element: Element, state: unknown, name: string) => void | CleanupFn
|
|
34
|
-
): HydrateFn {
|
|
35
|
-
// Store cleanup functions by element
|
|
36
|
-
const cleanups = new WeakMap<Element, CleanupFn>();
|
|
37
|
-
|
|
38
|
-
return (element: Element, state: unknown, name: string): void => {
|
|
39
|
-
// Skip if already hydrated
|
|
40
|
-
if ((element as HTMLElement).dataset.hydrated) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Run cleanup if re-hydrating (HMR scenario)
|
|
45
|
-
const existingCleanup = cleanups.get(element);
|
|
46
|
-
if (existingCleanup) {
|
|
47
|
-
existingCleanup();
|
|
48
|
-
cleanups.delete(element);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Run hydration
|
|
52
|
-
const cleanup = fn(element, state, name);
|
|
53
|
-
|
|
54
|
-
// Store cleanup if provided
|
|
55
|
-
if (typeof cleanup === 'function') {
|
|
56
|
-
cleanups.set(element, cleanup);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Mark as hydrated
|
|
60
|
-
(element as HTMLElement).dataset.hydrated = 'true';
|
|
61
|
-
};
|
|
62
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* onDelegate - Event delegation helper
|
|
3
|
-
*
|
|
4
|
-
* Attach event handlers using event delegation pattern.
|
|
5
|
-
* Efficient for multiple similar elements.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Set up delegated event handling
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* onDelegate(el, 'click', '[data-dialog-trigger]', () => {
|
|
14
|
-
* dialog.dataset.state = 'open';
|
|
15
|
-
* });
|
|
16
|
-
*
|
|
17
|
-
* onDelegate(el, 'click', '[data-dialog-close]', (e, target) => {
|
|
18
|
-
* dialog.dataset.state = 'closed';
|
|
19
|
-
* });
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
export function onDelegate<K extends keyof HTMLElementEventMap>(
|
|
23
|
-
root: Element,
|
|
24
|
-
event: K,
|
|
25
|
-
selector: string,
|
|
26
|
-
handler: (event: HTMLElementEventMap[K], target: Element) => void
|
|
27
|
-
): () => void {
|
|
28
|
-
const delegateHandler = (e: Event) => {
|
|
29
|
-
const target = (e.target as Element)?.closest(selector);
|
|
30
|
-
if (target && root.contains(target)) {
|
|
31
|
-
handler(e as HTMLElementEventMap[K], target);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
root.addEventListener(event, delegateHandler);
|
|
36
|
-
|
|
37
|
-
// Return cleanup function
|
|
38
|
-
return () => {
|
|
39
|
-
root.removeEventListener(event, delegateHandler);
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Attach click handler to multiple elements matching selector
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```ts
|
|
48
|
-
* onClick(el, '[data-tab-trigger]', (target) => {
|
|
49
|
-
* const tabId = target.dataset.tabId;
|
|
50
|
-
* // activate tab...
|
|
51
|
-
* });
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
export function onClick(
|
|
55
|
-
root: Element,
|
|
56
|
-
selector: string,
|
|
57
|
-
handler: (target: Element, event: MouseEvent) => void
|
|
58
|
-
): () => void {
|
|
59
|
-
return onDelegate(root, 'click', selector, (e, target) => {
|
|
60
|
-
handler(target, e);
|
|
61
|
-
});
|
|
62
|
-
}
|
package/src/hydration/drag.ts
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* drag - Horizontal drag helper for sliders/range inputs
|
|
3
|
-
*
|
|
4
|
-
* Handles mouse and touch drag interactions.
|
|
5
|
-
* Updates CSS custom property for visual positioning.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface DragOptions {
|
|
9
|
-
/**
|
|
10
|
-
* Selector for the track element (defines drag bounds).
|
|
11
|
-
* Required for calculating position.
|
|
12
|
-
*/
|
|
13
|
-
track: string;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* CSS custom property to update with percentage (0-100).
|
|
17
|
-
* @default '--slider-percent'
|
|
18
|
-
*/
|
|
19
|
-
cssProperty?: string;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Attribute to update with the calculated value.
|
|
23
|
-
* @default 'data-value'
|
|
24
|
-
*/
|
|
25
|
-
attribute?: string;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Minimum value.
|
|
29
|
-
* @default 0
|
|
30
|
-
*/
|
|
31
|
-
min?: number;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Maximum value.
|
|
35
|
-
* @default 100
|
|
36
|
-
*/
|
|
37
|
-
max?: number;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Step increment (for keyboard navigation).
|
|
41
|
-
* @default 1
|
|
42
|
-
*/
|
|
43
|
-
step?: number;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Selector for value display element.
|
|
47
|
-
* If provided, its textContent will be updated.
|
|
48
|
-
*/
|
|
49
|
-
display?: string;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Callback when value changes.
|
|
53
|
-
*/
|
|
54
|
-
onChange?: (value: number, percent: number, element: Element) => void;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Set up drag behavior for slider-like components
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* ```ts
|
|
62
|
-
* // Basic slider
|
|
63
|
-
* drag(el, '[data-slider]', {
|
|
64
|
-
* track: '[data-slider-track]',
|
|
65
|
-
* display: '[data-slider-value]'
|
|
66
|
-
* });
|
|
67
|
-
*
|
|
68
|
-
* // Custom range
|
|
69
|
-
* drag(el, '[data-range]', {
|
|
70
|
-
* track: '[data-range-track]',
|
|
71
|
-
* min: -50,
|
|
72
|
-
* max: 50,
|
|
73
|
-
* cssProperty: '--range-position',
|
|
74
|
-
* onChange: (value) => console.log('Value:', value)
|
|
75
|
-
* });
|
|
76
|
-
* ```
|
|
77
|
-
*/
|
|
78
|
-
export function drag(
|
|
79
|
-
root: Element,
|
|
80
|
-
selector: string,
|
|
81
|
-
options: DragOptions
|
|
82
|
-
): () => void {
|
|
83
|
-
const {
|
|
84
|
-
track: trackSelector,
|
|
85
|
-
cssProperty = '--slider-percent',
|
|
86
|
-
attribute = 'data-value',
|
|
87
|
-
min = 0,
|
|
88
|
-
max = 100,
|
|
89
|
-
step = 1,
|
|
90
|
-
display: displaySelector,
|
|
91
|
-
onChange,
|
|
92
|
-
} = options;
|
|
93
|
-
|
|
94
|
-
const cleanups: Array<() => void> = [];
|
|
95
|
-
|
|
96
|
-
root.querySelectorAll(selector).forEach((slider) => {
|
|
97
|
-
const track = slider.querySelector(trackSelector);
|
|
98
|
-
const thumb = slider.querySelector('[data-slider-thumb]');
|
|
99
|
-
const display = displaySelector ? root.querySelector(displaySelector) : null;
|
|
100
|
-
|
|
101
|
-
if (!track) return;
|
|
102
|
-
|
|
103
|
-
// Get initial value from attribute
|
|
104
|
-
let value = parseInt(slider.getAttribute(attribute) || '50', 10);
|
|
105
|
-
|
|
106
|
-
const update = (val: number) => {
|
|
107
|
-
// Clamp value
|
|
108
|
-
value = Math.max(min, Math.min(max, val));
|
|
109
|
-
const pct = ((value - min) / (max - min)) * 100;
|
|
110
|
-
|
|
111
|
-
// Update attribute
|
|
112
|
-
slider.setAttribute(attribute, String(value));
|
|
113
|
-
|
|
114
|
-
// Update aria
|
|
115
|
-
slider.setAttribute('aria-valuenow', String(value));
|
|
116
|
-
|
|
117
|
-
// Update CSS custom property
|
|
118
|
-
(slider as HTMLElement).style.setProperty(cssProperty, String(pct));
|
|
119
|
-
|
|
120
|
-
// Update display
|
|
121
|
-
if (display) {
|
|
122
|
-
display.textContent = String(value);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
onChange?.(value, pct, slider);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// Set initial CSS property
|
|
129
|
-
const initialPct = ((value - min) / (max - min)) * 100;
|
|
130
|
-
(slider as HTMLElement).style.setProperty(cssProperty, String(initialPct));
|
|
131
|
-
|
|
132
|
-
// Calculate value from position
|
|
133
|
-
const calcValue = (clientX: number): number => {
|
|
134
|
-
const rect = track.getBoundingClientRect();
|
|
135
|
-
const pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
136
|
-
return Math.round(min + pct * (max - min));
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// Drag handlers
|
|
140
|
-
const handleDrag = (e: MouseEvent | TouchEvent) => {
|
|
141
|
-
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
142
|
-
update(calcValue(clientX));
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const handleStart = (e: MouseEvent | TouchEvent) => {
|
|
146
|
-
e.preventDefault();
|
|
147
|
-
handleDrag(e);
|
|
148
|
-
|
|
149
|
-
const handleMove = (e: MouseEvent | TouchEvent) => handleDrag(e);
|
|
150
|
-
const handleEnd = () => {
|
|
151
|
-
document.removeEventListener('mousemove', handleMove);
|
|
152
|
-
document.removeEventListener('mouseup', handleEnd);
|
|
153
|
-
document.removeEventListener('touchmove', handleMove as EventListener);
|
|
154
|
-
document.removeEventListener('touchend', handleEnd);
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
document.addEventListener('mousemove', handleMove);
|
|
158
|
-
document.addEventListener('mouseup', handleEnd);
|
|
159
|
-
document.addEventListener('touchmove', handleMove as EventListener);
|
|
160
|
-
document.addEventListener('touchend', handleEnd);
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// Attach to track
|
|
164
|
-
track.addEventListener('mousedown', handleStart as EventListener);
|
|
165
|
-
track.addEventListener('touchstart', handleStart as EventListener);
|
|
166
|
-
|
|
167
|
-
// Attach to thumb if exists
|
|
168
|
-
if (thumb) {
|
|
169
|
-
thumb.addEventListener('mousedown', handleStart as EventListener);
|
|
170
|
-
thumb.addEventListener('touchstart', handleStart as EventListener);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Keyboard navigation
|
|
174
|
-
const handleKeydown = (e: KeyboardEvent) => {
|
|
175
|
-
switch (e.key) {
|
|
176
|
-
case 'ArrowRight':
|
|
177
|
-
case 'ArrowUp':
|
|
178
|
-
update(value + step);
|
|
179
|
-
e.preventDefault();
|
|
180
|
-
break;
|
|
181
|
-
case 'ArrowLeft':
|
|
182
|
-
case 'ArrowDown':
|
|
183
|
-
update(value - step);
|
|
184
|
-
e.preventDefault();
|
|
185
|
-
break;
|
|
186
|
-
case 'Home':
|
|
187
|
-
update(min);
|
|
188
|
-
e.preventDefault();
|
|
189
|
-
break;
|
|
190
|
-
case 'End':
|
|
191
|
-
update(max);
|
|
192
|
-
e.preventDefault();
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
if (thumb) {
|
|
198
|
-
thumb.addEventListener('keydown', handleKeydown as EventListener);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Cleanup
|
|
202
|
-
cleanups.push(() => {
|
|
203
|
-
track.removeEventListener('mousedown', handleStart as EventListener);
|
|
204
|
-
track.removeEventListener('touchstart', handleStart as EventListener);
|
|
205
|
-
if (thumb) {
|
|
206
|
-
thumb.removeEventListener('mousedown', handleStart as EventListener);
|
|
207
|
-
thumb.removeEventListener('touchstart', handleStart as EventListener);
|
|
208
|
-
thumb.removeEventListener('keydown', handleKeydown as EventListener);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
return () => cleanups.forEach((fn) => fn());
|
|
214
|
-
}
|
package/src/hydration/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @luna/hydration - SSR Hydration Helpers
|
|
3
|
-
*
|
|
4
|
-
* Composable helpers for hydrating SSR-rendered components.
|
|
5
|
-
* CSS-first approach: JS handles events, CSS handles visuals.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export { createHydrator, type HydrateFn, type CleanupFn } from './createHydrator';
|
|
9
|
-
export { toggle, type ToggleOptions } from './toggle';
|
|
10
|
-
export { drag, type DragOptions } from './drag';
|
|
11
|
-
export { onDelegate, onClick } from './delegate';
|
|
12
|
-
export { keyboard, onEscape, type KeyboardHandlers } from './keyboard';
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* keyboard - Keyboard event handler helper
|
|
3
|
-
*
|
|
4
|
-
* Map keyboard keys to handler functions.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export type KeyboardHandlers = {
|
|
8
|
-
[key: string]: (event: KeyboardEvent) => void;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Set up keyboard handlers on an element or document
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* // Handle Escape on document
|
|
17
|
-
* keyboard(document, {
|
|
18
|
-
* Escape: () => close()
|
|
19
|
-
* });
|
|
20
|
-
*
|
|
21
|
-
* // Handle arrow keys on a specific element
|
|
22
|
-
* keyboard(slider, {
|
|
23
|
-
* ArrowUp: () => increment(),
|
|
24
|
-
* ArrowDown: () => decrement(),
|
|
25
|
-
* ArrowLeft: () => decrement(),
|
|
26
|
-
* ArrowRight: () => increment()
|
|
27
|
-
* });
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export function keyboard(
|
|
31
|
-
target: Element | Document | null,
|
|
32
|
-
handlers: KeyboardHandlers
|
|
33
|
-
): () => void {
|
|
34
|
-
if (!target) return () => {};
|
|
35
|
-
|
|
36
|
-
const handler = (e: Event) => {
|
|
37
|
-
const key = (e as KeyboardEvent).key;
|
|
38
|
-
const fn = handlers[key];
|
|
39
|
-
if (fn) {
|
|
40
|
-
fn(e as KeyboardEvent);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
target.addEventListener('keydown', handler);
|
|
45
|
-
|
|
46
|
-
// Return cleanup function
|
|
47
|
-
return () => {
|
|
48
|
-
target.removeEventListener('keydown', handler);
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Handle Escape key to close something
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```ts
|
|
57
|
-
* onEscape(() => {
|
|
58
|
-
* dialog.dataset.state = 'closed';
|
|
59
|
-
* });
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
export function onEscape(handler: () => void): () => void {
|
|
63
|
-
return keyboard(document, { Escape: handler });
|
|
64
|
-
}
|