Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/cursor-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@solid-primitives/cursor": major
---

Migrate to Solid.js v2.0 and add new primitives

## Breaking Changes

**Peer dependency**: `solid-js@^2.0.0-beta.10` and `@solidjs/web@^2.0.0-beta.10` are now required.

- `isServer` now imported from `@solidjs/web` (not `solid-js/web`)
- `createElementCursor` and `createBodyCursor` updated to the split compute/apply effect pattern required by Solid 2.0 — cleanup is returned from the apply phase instead of using `onCleanup`

## New Exports

- `makeBodyCursor(cursor)` — sets cursor on body immediately, returns a cleanup function
- `makeElementCursor(target, cursor)` — sets cursor on an element immediately, returns a cleanup function
- `createDragCursor(target, options?)` — reactively sets `"grab"` on a target element and switches to `"grabbing"` on the body during pointer drag
- `cursorRef(cursor)` — ref factory for inline JSX use: `<div ref={cursorRef("pointer")}>`
90 changes: 73 additions & 17 deletions packages/cursor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
[![version](https://img.shields.io/npm/v/@solid-primitives/cursor?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/cursor)
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)

Two simple primitives for setting cursor css property reactively.
Primitives for setting the CSS cursor property reactively.

- [`createElementCursor`](#createelementcursor) - Set provided cursor to given HTML Element styles reactively.
- [`createBodyCursor`](#createbodycursor) - Set selected cursor to body element styles reactively.
- [`makeBodyCursor`](#makebodycursor) - Set cursor on body immediately; returns a cleanup function.
- [`makeElementCursor`](#makeelementcursor) - Set cursor on an element immediately; returns a cleanup function.
- [`createBodyCursor`](#createbodycursor) - Set cursor on body reactively.
- [`createElementCursor`](#createelementcursor) - Set cursor on a specific element reactively.
- [`createDragCursor`](#createdragcursor) - Show `grab`/`grabbing` cursors during pointer drag.
- [`cursorRef`](#cursorref) - Ref factory for inline JSX use.

## Installation

Expand All @@ -23,14 +27,50 @@ yarn add @solid-primitives/cursor
pnpm add @solid-primitives/cursor
```

## `createElementCursor`
## `makeBodyCursor`

Sets a cursor on the body element immediately and returns a cleanup function that restores the previous value. No reactive owner required.

```ts
import { makeBodyCursor } from "@solid-primitives/cursor";

// Show a loading cursor during an async operation
const restore = makeBodyCursor("wait");
await doSomething();
restore();
```

## `makeElementCursor`

Sets a cursor on a specific element immediately and returns a cleanup function that restores the previous value. No reactive owner required.

```ts
import { makeElementCursor } from "@solid-primitives/cursor";

const el = document.querySelector("#element")!;
const restore = makeElementCursor(el, "not-allowed");
// ... later
restore();
```

## `createBodyCursor`

Sets a cursor on the body element reactively. The cursor is removed when the owner is disposed or when the signal returns a falsy value.

```ts
import { createBodyCursor } from "@solid-primitives/cursor";

const [cursor, setCursor] = createSignal("pointer");
const [enabled, setEnabled] = createSignal(true);

createBodyCursor(() => enabled() && cursor());

Set provided cursor to given HTML Element styles reactively.
setCursor("help");
```

It takes two arguments:
## `createElementCursor`

- `element` - HTMLElement or a reactive signal returning one. Returning falsy value will unset the cursor.
- `cursor` - Cursor css property. E.g. "pointer", "grab", "zoom-in", "wait", etc.
Sets a cursor on a specific element reactively. Accepts an element or a signal returning one — returning a falsy value unsets the cursor.

```ts
import { createElementCursor } from "@solid-primitives/cursor";
Expand All @@ -44,23 +84,39 @@ createElementCursor(() => enabled() && target, cursor);
setCursor("help");
```

## `createBodyCursor`
## `createDragCursor`

Shows `"grab"` on a target element and switches to `"grabbing"` on the body during a pointer drag. Setting `"grabbing"` on the body ensures the cursor renders correctly everywhere during drag, not just over the target element.

```ts
import { createDragCursor } from "@solid-primitives/cursor";

Set selected cursor to body element styles reactively.
const [ref, setRef] = createSignal<HTMLElement>();

It takes only one argument:
createDragCursor(ref);

- `cursor` - Signal returing a cursor css property. E.g. "pointer", "grab", "zoom-in", "wait", etc. Returning falsy value will unset the cursor.
<div ref={setRef}>Drag me</div>
```

Custom cursor values can be provided via options:

```ts
import { createBodyCursor } from "@solid-primitives/cursor";
createDragCursor(el, { grab: "crosshair", grabbing: "move" });
```

const [cursor, setCursor] = createSignal("pointer");
const [enabled, setEnabled] = createSignal(true);
## `cursorRef`

createBodyCursor(() => enabled() && cursor());
A ref factory for setting a cursor inline in JSX. Accepts a static cursor value or a reactive signal. The cursor is removed when the component unmounts.

setCursor("help");
```tsx
import { cursorRef } from "@solid-primitives/cursor";

// Static
<div ref={cursorRef("pointer")}>...</div>;

// Reactive
const [cursor, setCursor] = createSignal<CursorProperty>("pointer");
<div ref={cursorRef(cursor)}>...</div>;
```

## Changelog
Expand Down
16 changes: 11 additions & 5 deletions packages/cursor/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@solid-primitives/cursor",
"version": "0.1.3",
"description": "Two simple primitives for setting cursor css property reactively.",
"version": "0.2.0",
"description": "Primitives for setting CSS cursor property reactively.",
"author": "Damian Tarnawski <gthetarnav@gmail.com>",
"contributors": [],
"license": "MIT",
Expand All @@ -17,8 +17,12 @@
"name": "cursor",
"stage": 0,
"list": [
"makeBodyCursor",
"makeElementCursor",
"createBodyCursor",
"createElementCursor",
"createBodyCursor"
"createDragCursor",
"cursorRef"
],
"category": "Utilities"
},
Expand Down Expand Up @@ -55,10 +59,12 @@
"@solid-primitives/utils": "workspace:^"
},
"peerDependencies": {
"solid-js": "^1.6.12"
"@solidjs/web": "^2.0.0-beta.10",
"solid-js": "^2.0.0-beta.10"
},
"typesVersions": {},
"devDependencies": {
"solid-js": "^1.9.7"
"@solidjs/web": "2.0.0-beta.10",
"solid-js": "2.0.0-beta.10"
}
}
144 changes: 133 additions & 11 deletions packages/cursor/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Accessor, createEffect, onCleanup } from "solid-js";
import { isServer } from "solid-js/web";
import { access, type FalsyValue, type MaybeAccessor } from "@solid-primitives/utils";
import { type Accessor, createEffect, createSignal } from "solid-js";
import { isServer } from "@solidjs/web";
import { access, noop, type FalsyValue, type MaybeAccessor } from "@solid-primitives/utils";

export type CursorProperty =
| "-moz-grab"
Expand Down Expand Up @@ -43,6 +43,47 @@ export type CursorProperty =
| "zoom-out"
| (string & {});

/**
* Set selected {@link cursor} to body element styles immediately.
*
* Returns a cleanup function that restores the previous cursor.
*
* @param cursor Cursor css property. E.g. "pointer", "grab", "zoom-in", "wait", etc.
*
* @example
* ```ts
* const restore = makeBodyCursor("wait");
* // ... async operation ...
* restore();
* ```
*/
export function makeBodyCursor(cursor: CursorProperty): VoidFunction {
if (isServer) return noop;
return makeElementCursor(document.body, cursor);
}

/**
* Set selected {@link cursor} to {@link target} element styles immediately.
*
* Returns a cleanup function that restores the previous cursor.
*
* @param target HTMLElement to set the cursor on.
* @param cursor Cursor css property. E.g. "pointer", "grab", "zoom-in", "wait", etc.
*
* @example
* ```ts
* const restore = makeElementCursor(el, "wait");
* // ... async operation ...
* restore();
* ```
*/
export function makeElementCursor(target: HTMLElement, cursor: CursorProperty): VoidFunction {
if (isServer) return noop;
const overwritten = target.style.cursor;
target.style.setProperty("cursor", cursor, "important");
return () => (target.style.cursor = overwritten);
}

/**
* Set selected {@link cursor} to {@link target} styles reactively.
*
Expand All @@ -66,14 +107,21 @@ export function createElementCursor(
): void {
if (isServer) return;

createEffect(() => {
const el = access(target);
const cursorValue = access(cursor);
type State = { el: HTMLElement | FalsyValue; cursorValue: CursorProperty };

const compute = (): State => ({
el: access(target),
cursorValue: access(cursor),
});

const apply = ({ el, cursorValue }: State) => {
if (!el) return;
const overwritten = el.style.cursor;
el.style.setProperty("cursor", cursorValue, "important");
onCleanup(() => (el.style.cursor = overwritten));
});
return () => (el.style.cursor = overwritten);
};

createEffect(compute, apply);
}

/**
Expand All @@ -94,11 +142,85 @@ export function createElementCursor(
export function createBodyCursor(cursor: Accessor<CursorProperty | FalsyValue>): void {
if (isServer) return;

createEffect(() => {
const cursorValue = cursor();
createEffect(cursor, cursorValue => {
if (!cursorValue) return;
const overwritten = document.body.style.cursor;
document.body.style.setProperty("cursor", cursorValue, "important");
onCleanup(() => (document.body.style.cursor = overwritten));
return () => (document.body.style.cursor = overwritten);
});
}

/**
* Reactively sets "grab" cursor on {@link target} and switches to "grabbing" on the body during drag.
*
* Setting "grabbing" on the body ensures the cursor renders correctly everywhere during drag,
* not just over the target element.
*
* @param target HTMLElement or a reactive signal returning one. Returning falsy value will disable the cursor.
* @param options Optional overrides for the grab and grabbing cursor values.
*
* @example
* ```ts
* const [ref, setRef] = createSignal<HTMLElement>();
*
* createDragCursor(ref);
*
* <div ref={setRef}>Drag me</div>
* ```
*/
export function createDragCursor(
target: Accessor<HTMLElement | FalsyValue> | HTMLElement,
options?: { grab?: CursorProperty; grabbing?: CursorProperty },
): void {
if (isServer) return;

const grab = options?.grab ?? "grab";
const grabbing = options?.grabbing ?? "grabbing";
const [dragging, setDragging] = createSignal(false);

// During drag, "grabbing" is set on body so it shows globally.
// "grab" is cleared from the element so the body cursor can inherit through —
// element inline styles (even without !important) would otherwise win over body.
createBodyCursor(() => dragging() && grabbing);
createElementCursor(() => {
const el = access(target);
return dragging() ? false : el;
}, grab);

createEffect(
() => access(target),
el => {
if (!el) return;
const onDown = () => setDragging(true);
const onUp = () => setDragging(false);
el.addEventListener("pointerdown", onDown);
document.addEventListener("pointerup", onUp);
document.addEventListener("pointercancel", onUp);
return () => {
el.removeEventListener("pointerdown", onDown);
document.removeEventListener("pointerup", onUp);
document.removeEventListener("pointercancel", onUp);
};
},
);
}

/**
* Returns a ref callback that sets a cursor on the element it is attached to.
*
* Accepts a static cursor value or a reactive signal. The cursor is removed when the
* component unmounts.
*
* @example
* ```tsx
* // static
* <div ref={cursorRef("pointer")}>...</div>
*
* // reactive
* const [cursor, setCursor] = createSignal<CursorProperty>("pointer");
* <div ref={cursorRef(cursor)}>...</div>
* ```
*/
export function cursorRef(cursor: MaybeAccessor<CursorProperty>): (el: HTMLElement) => void {
return el => createElementCursor(el, cursor);
}
Loading