drift-slider-react 0.1.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/README.md +53 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +99 -0
- package/package.json +38 -0
- package/src/index.tsx +172 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# drift-slider-react
|
|
2
|
+
|
|
3
|
+
Official React wrapper for [DriftSlider](https://github.com/hanfour/drift-slider).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i drift-slider drift-slider-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
'use client';
|
|
15
|
+
import { DriftSlider, Slide } from 'drift-slider-react';
|
|
16
|
+
import { Navigation, Pagination } from 'drift-slider/modules';
|
|
17
|
+
import 'drift-slider/css/bundle';
|
|
18
|
+
|
|
19
|
+
export function Carousel() {
|
|
20
|
+
return (
|
|
21
|
+
<DriftSlider
|
|
22
|
+
options={{ loop: true, navigation: true, pagination: true }}
|
|
23
|
+
modules={[Navigation, Pagination]}
|
|
24
|
+
onSlideChange={(s) => console.log(s.realIndex)}
|
|
25
|
+
>
|
|
26
|
+
<Slide>Slide 1</Slide>
|
|
27
|
+
<Slide>Slide 2</Slide>
|
|
28
|
+
</DriftSlider>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Imperative control (ref)
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
const ref = useRef<DriftSliderHandle>(null);
|
|
37
|
+
// ref.current.slideNext(); ref.current.slideTo(2); ref.current.update();
|
|
38
|
+
<DriftSlider ref={ref}>…</DriftSlider>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Headless hook
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
const [containerRef] = useDriftSlider({ loop: true });
|
|
45
|
+
return <div className="drift-slider" ref={containerRef}>…your scaffold…</div>;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Notes
|
|
49
|
+
|
|
50
|
+
- SSR-safe: the slider initialises in `useEffect` (client only); add `'use client'` in Next.js App Router.
|
|
51
|
+
- Slides should carry stable `key`s; changing the key set re-runs `update()`.
|
|
52
|
+
- Dynamic per-slide add/remove without recompute is not supported (use `ref.update()`).
|
|
53
|
+
- Memoize `options` and `modules` (e.g. with `useMemo`) — the slider re-initialises when their identity changes, and an inline `{}`/`[]` literal changes every render, which would tear down and recreate the slider (and drop event handlers between renders).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties, ReactNode, LiHTMLAttributes } from 'react';
|
|
3
|
+
import CoreDriftSlider, { DriftSliderEvents, DriftSliderOptions, DriftSliderModule } from 'drift-slider';
|
|
4
|
+
|
|
5
|
+
declare function cx(...parts: Array<string | false | undefined>): string;
|
|
6
|
+
interface SlideProps extends LiHTMLAttributes<HTMLLIElement> {
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
declare function Slide({ className, children, ...rest }: SlideProps): react.JSX.Element;
|
|
10
|
+
declare function useDriftSlider(options?: DriftSliderOptions, deps?: unknown[]): readonly [react.RefObject<HTMLDivElement>, react.MutableRefObject<CoreDriftSlider | null>];
|
|
11
|
+
interface DriftSliderHandle {
|
|
12
|
+
slideTo: (...args: Parameters<CoreDriftSlider['slideTo']>) => CoreDriftSlider | undefined;
|
|
13
|
+
slideNext: (...args: Parameters<CoreDriftSlider['slideNext']>) => CoreDriftSlider | undefined;
|
|
14
|
+
slidePrev: (...args: Parameters<CoreDriftSlider['slidePrev']>) => CoreDriftSlider | undefined;
|
|
15
|
+
update: () => void;
|
|
16
|
+
instance: CoreDriftSlider | null;
|
|
17
|
+
}
|
|
18
|
+
type EventShortcuts = {
|
|
19
|
+
onInit?: DriftSliderEvents['init'];
|
|
20
|
+
onSlideChange?: DriftSliderEvents['slideChange'];
|
|
21
|
+
onReachBeginning?: DriftSliderEvents['reachBeginning'];
|
|
22
|
+
onReachEnd?: DriftSliderEvents['reachEnd'];
|
|
23
|
+
onTouchStart?: DriftSliderEvents['touchStart'];
|
|
24
|
+
onTouchEnd?: DriftSliderEvents['touchEnd'];
|
|
25
|
+
};
|
|
26
|
+
interface DriftSliderProps extends EventShortcuts {
|
|
27
|
+
options?: DriftSliderOptions;
|
|
28
|
+
modules?: DriftSliderModule[];
|
|
29
|
+
on?: Partial<DriftSliderEvents>;
|
|
30
|
+
className?: string;
|
|
31
|
+
style?: CSSProperties;
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
}
|
|
34
|
+
declare const DriftSlider: react.ForwardRefExoticComponent<DriftSliderProps & react.RefAttributes<DriftSliderHandle>>;
|
|
35
|
+
|
|
36
|
+
export { DriftSlider, type DriftSliderHandle, type DriftSliderProps, Slide, type SlideProps, cx, DriftSlider as default, useDriftSlider };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// src/index.tsx
|
|
4
|
+
import { useEffect, useImperativeHandle, useRef, forwardRef, Children, isValidElement } from "react";
|
|
5
|
+
import CoreDriftSlider from "drift-slider";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
function cx(...parts) {
|
|
8
|
+
return parts.filter(Boolean).join(" ");
|
|
9
|
+
}
|
|
10
|
+
function childrenKeys(children) {
|
|
11
|
+
return Children.toArray(children).map((c, i) => isValidElement(c) && c.key != null ? String(c.key) : `__i${i}`).join("|");
|
|
12
|
+
}
|
|
13
|
+
function Slide({ className, children, ...rest }) {
|
|
14
|
+
return /* @__PURE__ */ jsx("li", { className: cx("drift-slide", className), ...rest, children });
|
|
15
|
+
}
|
|
16
|
+
function useDriftSlider(options, deps = []) {
|
|
17
|
+
const containerRef = useRef(null);
|
|
18
|
+
const sliderRef = useRef(null);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const el = containerRef.current;
|
|
21
|
+
if (!el) return;
|
|
22
|
+
const slider = new CoreDriftSlider(el, options);
|
|
23
|
+
sliderRef.current = slider;
|
|
24
|
+
return () => {
|
|
25
|
+
slider.destroy();
|
|
26
|
+
sliderRef.current = null;
|
|
27
|
+
};
|
|
28
|
+
}, deps);
|
|
29
|
+
return [containerRef, sliderRef];
|
|
30
|
+
}
|
|
31
|
+
var SHORTCUTS = {
|
|
32
|
+
onInit: "init",
|
|
33
|
+
onSlideChange: "slideChange",
|
|
34
|
+
onReachBeginning: "reachBeginning",
|
|
35
|
+
onReachEnd: "reachEnd",
|
|
36
|
+
onTouchStart: "touchStart",
|
|
37
|
+
onTouchEnd: "touchEnd"
|
|
38
|
+
};
|
|
39
|
+
function collectHandlers(optionsOn, onProp, shortcuts) {
|
|
40
|
+
const handlers = { ...optionsOn, ...onProp };
|
|
41
|
+
Object.keys(SHORTCUTS).forEach((key) => {
|
|
42
|
+
const handler = shortcuts[key];
|
|
43
|
+
if (handler) handlers[SHORTCUTS[key]] = handler;
|
|
44
|
+
});
|
|
45
|
+
return handlers;
|
|
46
|
+
}
|
|
47
|
+
function forwardEvents(handlers, handlersRef) {
|
|
48
|
+
const on = {};
|
|
49
|
+
(/* @__PURE__ */ new Set([...Object.values(SHORTCUTS), ...Object.keys(handlers)])).forEach((name) => {
|
|
50
|
+
on[name] = (...args) => {
|
|
51
|
+
const map = handlersRef.current;
|
|
52
|
+
map[name]?.(...args);
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
return on;
|
|
56
|
+
}
|
|
57
|
+
var DriftSlider = forwardRef(
|
|
58
|
+
function DriftSlider2(props, ref) {
|
|
59
|
+
const { options, modules, on, className, style, children, ...shortcuts } = props;
|
|
60
|
+
const handlers = collectHandlers(options?.on, on, shortcuts);
|
|
61
|
+
const handlersRef = useRef(handlers);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
handlersRef.current = handlers;
|
|
64
|
+
});
|
|
65
|
+
const sliderOptions = {
|
|
66
|
+
...options,
|
|
67
|
+
modules: [...options?.modules ?? [], ...modules ?? []],
|
|
68
|
+
on: forwardEvents(handlers, handlersRef)
|
|
69
|
+
};
|
|
70
|
+
const [containerRef, sliderRef] = useDriftSlider(sliderOptions, [options, modules]);
|
|
71
|
+
const keySig = childrenKeys(children);
|
|
72
|
+
const didMount = useRef(false);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!didMount.current) {
|
|
75
|
+
didMount.current = true;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
sliderRef.current?.update();
|
|
79
|
+
}, [keySig]);
|
|
80
|
+
useImperativeHandle(ref, () => ({
|
|
81
|
+
slideTo: (...args) => sliderRef.current?.slideTo(...args),
|
|
82
|
+
slideNext: (...args) => sliderRef.current?.slideNext(...args),
|
|
83
|
+
slidePrev: (...args) => sliderRef.current?.slidePrev(...args),
|
|
84
|
+
update: () => sliderRef.current?.update(),
|
|
85
|
+
get instance() {
|
|
86
|
+
return sliderRef.current;
|
|
87
|
+
}
|
|
88
|
+
}), [sliderRef]);
|
|
89
|
+
return /* @__PURE__ */ jsx("div", { className: cx("drift-slider", className), style, ref: containerRef, children: /* @__PURE__ */ jsx("div", { className: "drift-track", children: /* @__PURE__ */ jsx("ul", { className: "drift-list", children }) }) });
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
var index_default = DriftSlider;
|
|
93
|
+
export {
|
|
94
|
+
DriftSlider,
|
|
95
|
+
Slide,
|
|
96
|
+
cx,
|
|
97
|
+
index_default as default,
|
|
98
|
+
useDriftSlider
|
|
99
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "drift-slider-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official React wrapper for DriftSlider",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }
|
|
11
|
+
},
|
|
12
|
+
"files": ["dist", "src"],
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": ">=18",
|
|
21
|
+
"react-dom": ">=18",
|
|
22
|
+
"drift-slider": ">=0.7.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@testing-library/react": "^16.0.0",
|
|
26
|
+
"@types/react": "^18.3.0",
|
|
27
|
+
"@types/react-dom": "^18.3.0",
|
|
28
|
+
"drift-slider": "*",
|
|
29
|
+
"react": "^18.3.0",
|
|
30
|
+
"react-dom": "^18.3.0",
|
|
31
|
+
"tsup": "^8.0.0",
|
|
32
|
+
"typescript": "^5.4.0",
|
|
33
|
+
"vitest": "^4.1.0",
|
|
34
|
+
"jsdom": "^29.0.0"
|
|
35
|
+
},
|
|
36
|
+
"keywords": ["react", "slider", "carousel", "drift-slider"],
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { useEffect, useImperativeHandle, useRef, forwardRef, Children, isValidElement } from 'react';
|
|
2
|
+
import type { CSSProperties, LiHTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
import CoreDriftSlider, {
|
|
4
|
+
type DriftSliderOptions,
|
|
5
|
+
type DriftSliderModule,
|
|
6
|
+
type DriftSliderEvents,
|
|
7
|
+
} from 'drift-slider';
|
|
8
|
+
|
|
9
|
+
export function cx(...parts: Array<string | false | undefined>): string {
|
|
10
|
+
return parts.filter(Boolean).join(' ');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function childrenKeys(children: ReactNode): string {
|
|
14
|
+
return Children.toArray(children)
|
|
15
|
+
.map((c, i) => (isValidElement(c) && c.key != null ? String(c.key) : `__i${i}`))
|
|
16
|
+
.join('|');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SlideProps extends LiHTMLAttributes<HTMLLIElement> {
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function Slide({ className, children, ...rest }: SlideProps) {
|
|
24
|
+
return (
|
|
25
|
+
<li className={cx('drift-slide', className)} {...rest}>
|
|
26
|
+
{children}
|
|
27
|
+
</li>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useDriftSlider(
|
|
32
|
+
options?: DriftSliderOptions,
|
|
33
|
+
deps: unknown[] = [],
|
|
34
|
+
) {
|
|
35
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
36
|
+
const sliderRef = useRef<CoreDriftSlider | null>(null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const el = containerRef.current;
|
|
40
|
+
if (!el) return;
|
|
41
|
+
const slider = new CoreDriftSlider(el, options);
|
|
42
|
+
sliderRef.current = slider;
|
|
43
|
+
return () => {
|
|
44
|
+
slider.destroy();
|
|
45
|
+
sliderRef.current = null;
|
|
46
|
+
};
|
|
47
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
48
|
+
}, deps);
|
|
49
|
+
|
|
50
|
+
return [containerRef, sliderRef] as const;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DriftSliderHandle {
|
|
54
|
+
slideTo: (...args: Parameters<CoreDriftSlider['slideTo']>) => CoreDriftSlider | undefined;
|
|
55
|
+
slideNext: (...args: Parameters<CoreDriftSlider['slideNext']>) => CoreDriftSlider | undefined;
|
|
56
|
+
slidePrev: (...args: Parameters<CoreDriftSlider['slidePrev']>) => CoreDriftSlider | undefined;
|
|
57
|
+
update: () => void;
|
|
58
|
+
instance: CoreDriftSlider | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type EventShortcuts = {
|
|
62
|
+
onInit?: DriftSliderEvents['init'];
|
|
63
|
+
onSlideChange?: DriftSliderEvents['slideChange'];
|
|
64
|
+
onReachBeginning?: DriftSliderEvents['reachBeginning'];
|
|
65
|
+
onReachEnd?: DriftSliderEvents['reachEnd'];
|
|
66
|
+
onTouchStart?: DriftSliderEvents['touchStart'];
|
|
67
|
+
onTouchEnd?: DriftSliderEvents['touchEnd'];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const SHORTCUTS: Record<keyof EventShortcuts, keyof DriftSliderEvents> = {
|
|
71
|
+
onInit: 'init',
|
|
72
|
+
onSlideChange: 'slideChange',
|
|
73
|
+
onReachBeginning: 'reachBeginning',
|
|
74
|
+
onReachEnd: 'reachEnd',
|
|
75
|
+
onTouchStart: 'touchStart',
|
|
76
|
+
onTouchEnd: 'touchEnd',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Merge `options.on`, the `on` prop, and the shortcut props into one event map.
|
|
80
|
+
function collectHandlers(
|
|
81
|
+
optionsOn: Partial<DriftSliderEvents> | undefined,
|
|
82
|
+
onProp: Partial<DriftSliderEvents> | undefined,
|
|
83
|
+
shortcuts: EventShortcuts,
|
|
84
|
+
): Partial<DriftSliderEvents> {
|
|
85
|
+
const handlers: Partial<DriftSliderEvents> = { ...optionsOn, ...onProp };
|
|
86
|
+
(Object.keys(SHORTCUTS) as (keyof EventShortcuts)[]).forEach((key) => {
|
|
87
|
+
const handler = shortcuts[key];
|
|
88
|
+
if (handler) (handlers as Record<string, unknown>)[SHORTCUTS[key]] = handler;
|
|
89
|
+
});
|
|
90
|
+
return handlers;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Build a stable `on` map whose forwarders read the latest handler from the ref
|
|
94
|
+
// on each emit — so changing a handler prop takes effect without re-init. Covers
|
|
95
|
+
// the shortcut events plus any keys present on the handler map.
|
|
96
|
+
function forwardEvents(
|
|
97
|
+
handlers: Partial<DriftSliderEvents>,
|
|
98
|
+
handlersRef: { current: Partial<DriftSliderEvents> },
|
|
99
|
+
): Partial<DriftSliderEvents> {
|
|
100
|
+
const on: Record<string, (...args: unknown[]) => void> = {};
|
|
101
|
+
new Set<string>([...Object.values(SHORTCUTS), ...Object.keys(handlers)]).forEach((name) => {
|
|
102
|
+
on[name] = (...args) => {
|
|
103
|
+
const map = handlersRef.current as Record<string, ((...a: unknown[]) => void) | undefined>;
|
|
104
|
+
map[name]?.(...args);
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
return on as Partial<DriftSliderEvents>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface DriftSliderProps extends EventShortcuts {
|
|
111
|
+
options?: DriftSliderOptions;
|
|
112
|
+
modules?: DriftSliderModule[];
|
|
113
|
+
on?: Partial<DriftSliderEvents>;
|
|
114
|
+
className?: string;
|
|
115
|
+
style?: CSSProperties;
|
|
116
|
+
children?: ReactNode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const DriftSlider = forwardRef<DriftSliderHandle, DriftSliderProps>(
|
|
120
|
+
function DriftSlider(props, ref) {
|
|
121
|
+
const { options, modules, on, className, style, children, ...shortcuts } = props;
|
|
122
|
+
|
|
123
|
+
// Keep the latest handlers in a ref so changing a handler prop takes effect
|
|
124
|
+
// without tearing the slider down and recreating it.
|
|
125
|
+
const handlers = collectHandlers(options?.on, on, shortcuts as EventShortcuts);
|
|
126
|
+
const handlersRef = useRef(handlers);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
handlersRef.current = handlers;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const sliderOptions: DriftSliderOptions = {
|
|
132
|
+
...options,
|
|
133
|
+
modules: [...(options?.modules ?? []), ...(modules ?? [])],
|
|
134
|
+
on: forwardEvents(handlers, handlersRef),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Compose the lifecycle from the shared hook; re-init only when
|
|
138
|
+
// options/modules identity changes.
|
|
139
|
+
const [containerRef, sliderRef] = useDriftSlider(sliderOptions, [options, modules]);
|
|
140
|
+
|
|
141
|
+
// Recompute on children key-set changes (skip the initial mount).
|
|
142
|
+
const keySig = childrenKeys(children);
|
|
143
|
+
const didMount = useRef(false);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (!didMount.current) {
|
|
146
|
+
didMount.current = true;
|
|
147
|
+
return; // initial render already built the right DOM
|
|
148
|
+
}
|
|
149
|
+
sliderRef.current?.update();
|
|
150
|
+
}, [keySig]);
|
|
151
|
+
|
|
152
|
+
useImperativeHandle(ref, () => ({
|
|
153
|
+
slideTo: (...args) => sliderRef.current?.slideTo(...args),
|
|
154
|
+
slideNext: (...args) => sliderRef.current?.slideNext(...args),
|
|
155
|
+
slidePrev: (...args) => sliderRef.current?.slidePrev(...args),
|
|
156
|
+
update: () => sliderRef.current?.update(),
|
|
157
|
+
get instance() {
|
|
158
|
+
return sliderRef.current;
|
|
159
|
+
},
|
|
160
|
+
}), [sliderRef]);
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className={cx('drift-slider', className)} style={style} ref={containerRef}>
|
|
164
|
+
<div className="drift-track">
|
|
165
|
+
<ul className="drift-list">{children}</ul>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
export default DriftSlider;
|