From 8cfd3f83992155cb0118fc0f43b76b13f2a946e6 Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Mon, 4 May 2026 09:38:17 -0400 Subject: [PATCH 1/2] Migrate to Solid 2.0 and minor API enhancements --- .changeset/cursor-solid2-migration.md | 19 +++ packages/cursor/README.md | 90 +++++++++--- packages/cursor/package.json | 16 ++- packages/cursor/src/index.ts | 144 +++++++++++++++++-- packages/cursor/test/index.test.ts | 193 +++++++++++++++++++++++++- pnpm-lock.yaml | 34 ++++- 6 files changed, 459 insertions(+), 37 deletions(-) create mode 100644 .changeset/cursor-solid2-migration.md diff --git a/.changeset/cursor-solid2-migration.md b/.changeset/cursor-solid2-migration.md new file mode 100644 index 000000000..c5a0427a8 --- /dev/null +++ b/.changeset/cursor-solid2-migration.md @@ -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: `
` diff --git a/packages/cursor/README.md b/packages/cursor/README.md index e01d17576..ab29ddfc3 100644 --- a/packages/cursor/README.md +++ b/packages/cursor/README.md @@ -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 @@ -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"; @@ -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(); -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. +
Drag me
+``` + +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 +
...
; + +// Reactive +const [cursor, setCursor] = createSignal("pointer"); +
...
; ``` ## Changelog diff --git a/packages/cursor/package.json b/packages/cursor/package.json index ca52fabee..aa94e4c71 100644 --- a/packages/cursor/package.json +++ b/packages/cursor/package.json @@ -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 ", "contributors": [], "license": "MIT", @@ -17,8 +17,12 @@ "name": "cursor", "stage": 0, "list": [ + "makeBodyCursor", + "makeElementCursor", + "createBodyCursor", "createElementCursor", - "createBodyCursor" + "createDragCursor", + "cursorRef" ], "category": "Utilities" }, @@ -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" } } diff --git a/packages/cursor/src/index.ts b/packages/cursor/src/index.ts index 6420521bb..1f35c6474 100644 --- a/packages/cursor/src/index.ts +++ b/packages/cursor/src/index.ts @@ -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" @@ -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. * @@ -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); } /** @@ -94,11 +142,85 @@ export function createElementCursor( export function createBodyCursor(cursor: Accessor): 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(); + * + * createDragCursor(ref); + * + *
Drag me
+ * ``` + */ +export function createDragCursor( + target: Accessor | 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 + *
...
+ * + * // reactive + * const [cursor, setCursor] = createSignal("pointer"); + *
...
+ * ``` + */ +export function cursorRef(cursor: MaybeAccessor): (el: HTMLElement) => void { + return el => createElementCursor(el, cursor); +} diff --git a/packages/cursor/test/index.test.ts b/packages/cursor/test/index.test.ts index 019cb787c..8d3fff1ef 100644 --- a/packages/cursor/test/index.test.ts +++ b/packages/cursor/test/index.test.ts @@ -1,6 +1,54 @@ import { describe, test, expect } from "vitest"; -import { createRoot, createSignal } from "solid-js"; -import { createBodyCursor, createElementCursor, CursorProperty } from "../src/index.js"; +import { createRoot, createSignal, flush } from "solid-js"; +import { + createBodyCursor, + createDragCursor, + createElementCursor, + cursorRef, + makeBodyCursor, + makeElementCursor, + type CursorProperty, +} from "../src/index.js"; + +describe("makeBodyCursor", () => { + test("sets cursor and returns cleanup", () => { + const restore = makeBodyCursor("pointer"); + expect(document.body.style.cursor).toBe("pointer"); + restore(); + expect(document.body.style.cursor).toBe(""); + }); + + test("restores nested cursors in stack order", () => { + const restore1 = makeBodyCursor("pointer"); + const restore2 = makeBodyCursor("help"); + expect(document.body.style.cursor).toBe("help"); + restore2(); + expect(document.body.style.cursor).toBe("pointer"); + restore1(); + expect(document.body.style.cursor).toBe(""); + }); +}); + +describe("makeElementCursor", () => { + test("sets cursor on element and returns cleanup", () => { + const el = document.createElement("div"); + const restore = makeElementCursor(el, "pointer"); + expect(el.style.cursor).toBe("pointer"); + restore(); + expect(el.style.cursor).toBe(""); + }); + + test("restores previous cursor value", () => { + const el = document.createElement("div"); + const restore1 = makeElementCursor(el, "pointer"); + const restore2 = makeElementCursor(el, "help"); + expect(el.style.cursor).toBe("help"); + restore2(); + expect(el.style.cursor).toBe("pointer"); + restore1(); + expect(el.style.cursor).toBe(""); + }); +}); describe("createBodyCursor", () => { test("switches previous cursor to provided one", () => { @@ -11,16 +59,20 @@ describe("createBodyCursor", () => { createBodyCursor(() => enabled() && cursor()); return dispose; }); + flush(); expect(document.body.style.cursor).toBe("pointer"); setCursor("help"); + flush(); expect(document.body.style.cursor).toBe("help"); setEnabled(false); + flush(); expect(document.body.style.cursor, "unsets cursor").toBe(""); setEnabled(true); + flush(); expect(document.body.style.cursor).toBe("help"); dispose(); @@ -38,16 +90,20 @@ describe("createElementCursor", () => { createElementCursor(() => enabled() && div, cursor); return dispose; }); + flush(); expect(div.style.cursor).toBe("pointer"); setCursor("help"); + flush(); expect(div.style.cursor).toBe("help"); setEnabled(false); + flush(); expect(div.style.cursor, "unsets cursor").toBe(""); setEnabled(true); + flush(); expect(div.style.cursor).toBe("help"); dispose(); @@ -65,28 +121,35 @@ describe("createElementCursor", () => { createElementCursor(() => enabled() && target(), cursor); return dispose; }); + flush(); expect(div1.style.cursor).toBe("pointer"); setCursor("help"); + flush(); expect(div1.style.cursor).toBe("help"); setTarget(div2); + flush(); expect(div1.style.cursor).toBe(""); expect(div2.style.cursor).toBe("help"); setEnabled(false); + flush(); expect(div2.style.cursor).toBe(""); setTarget(div1); + flush(); expect(div1.style.cursor).toBe(""); expect(div2.style.cursor).toBe(""); setCursor("pointer"); + flush(); expect(div1.style.cursor).toBe(""); expect(div2.style.cursor).toBe(""); setEnabled(true); + flush(); expect(div1.style.cursor).toBe("pointer"); expect(div2.style.cursor).toBe(""); @@ -94,3 +157,129 @@ describe("createElementCursor", () => { expect(div1.style.cursor).toBe(""); }); }); + +describe("createDragCursor", () => { + test("shows grab/grabbing cursors during drag", () => { + const el = document.createElement("div"); + + const dispose = createRoot(dispose => { + createDragCursor(el); + return dispose; + }); + flush(); + + expect(el.style.cursor).toBe("grab"); + expect(document.body.style.cursor).toBe(""); + + el.dispatchEvent(new Event("pointerdown")); + flush(); + expect(document.body.style.cursor).toBe("grabbing"); + expect(el.style.cursor).toBe(""); + + document.dispatchEvent(new Event("pointerup")); + flush(); + expect(document.body.style.cursor).toBe(""); + expect(el.style.cursor).toBe("grab"); + + dispose(); + expect(el.style.cursor).toBe(""); + }); + + test("resets on pointercancel", () => { + const el = document.createElement("div"); + + const dispose = createRoot(dispose => { + createDragCursor(el); + return dispose; + }); + flush(); + + el.dispatchEvent(new Event("pointerdown")); + flush(); + expect(document.body.style.cursor).toBe("grabbing"); + + document.dispatchEvent(new Event("pointercancel")); + flush(); + expect(document.body.style.cursor).toBe(""); + expect(el.style.cursor).toBe("grab"); + + dispose(); + }); + + test("supports custom cursor values", () => { + const el = document.createElement("div"); + + const dispose = createRoot(dispose => { + createDragCursor(el, { grab: "crosshair", grabbing: "move" }); + return dispose; + }); + flush(); + + expect(el.style.cursor).toBe("crosshair"); + + el.dispatchEvent(new Event("pointerdown")); + flush(); + expect(document.body.style.cursor).toBe("move"); + + document.dispatchEvent(new Event("pointerup")); + flush(); + expect(el.style.cursor).toBe("crosshair"); + + dispose(); + }); + + test("cleans up listeners and cursors on dispose", () => { + const el = document.createElement("div"); + + const dispose = createRoot(dispose => { + createDragCursor(el); + return dispose; + }); + flush(); + + dispose(); + expect(el.style.cursor).toBe(""); + expect(document.body.style.cursor).toBe(""); + + el.dispatchEvent(new Event("pointerdown")); + flush(); + expect(document.body.style.cursor).toBe(""); + }); +}); + +describe("cursorRef", () => { + test("applies cursor to element", () => { + const el = document.createElement("div"); + + const dispose = createRoot(dispose => { + cursorRef("pointer")(el); + return dispose; + }); + flush(); + + expect(el.style.cursor).toBe("pointer"); + + dispose(); + expect(el.style.cursor).toBe(""); + }); + + test("reacts to cursor signal changes", () => { + const el = document.createElement("div"); + const [cursor, setCursor] = createSignal("pointer"); + + const dispose = createRoot(dispose => { + cursorRef(cursor)(el); + return dispose; + }); + flush(); + + expect(el.style.cursor).toBe("pointer"); + + setCursor("help"); + flush(); + expect(el.style.cursor).toBe("help"); + + dispose(); + expect(el.style.cursor).toBe(""); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 726871879..ef69d507f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -211,9 +211,12 @@ importers: specifier: workspace:^ version: link:../utils devDependencies: + '@solidjs/web': + specifier: 2.0.0-beta.10 + version: 2.0.0-beta.10(solid-js@2.0.0-beta.10) solid-js: - specifier: ^1.9.7 - version: 1.9.7 + specifier: 2.0.0-beta.10 + version: 2.0.0-beta.10 packages/date: dependencies: @@ -2366,36 +2369,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-wasm@2.3.0': resolution: {integrity: sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA==} @@ -2506,36 +2515,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} @@ -2672,56 +2687,67 @@ packages: resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.43.0': resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.43.0': resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.43.0': resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.43.0': resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.43.0': resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.43.0': resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.43.0': resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.43.0': resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.43.0': resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.43.0': resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} @@ -5208,24 +5234,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} From b3b4f6e0bfd35bb424621a61e54cc0fe400c3620 Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Sat, 23 May 2026 17:55:46 -0400 Subject: [PATCH 2/2] Separate types and apply CR improvements --- packages/cursor/README.md | 2 +- packages/cursor/package.json | 8 +-- packages/cursor/src/index.ts | 74 +++++++++-------------- packages/cursor/src/types.ts | 40 +++++++++++++ pnpm-lock.yaml | 113 ++--------------------------------- 5 files changed, 76 insertions(+), 161 deletions(-) create mode 100644 packages/cursor/src/types.ts diff --git a/packages/cursor/README.md b/packages/cursor/README.md index ab29ddfc3..9c8a752a4 100644 --- a/packages/cursor/README.md +++ b/packages/cursor/README.md @@ -8,7 +8,7 @@ [![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) -Primitives for setting the CSS cursor property reactively. +Utilities for setting the CSS cursor property via reactive primitives (`createBodyCursor`, `createElementCursor`) and imperative APIs (`makeBodyCursor`, `makeElementCursor`). - [`makeBodyCursor`](#makebodycursor) - Set cursor on body immediately; returns a cleanup function. - [`makeElementCursor`](#makeelementcursor) - Set cursor on an element immediately; returns a cleanup function. diff --git a/packages/cursor/package.json b/packages/cursor/package.json index aa94e4c71..3527088ee 100644 --- a/packages/cursor/package.json +++ b/packages/cursor/package.json @@ -59,12 +59,12 @@ "@solid-primitives/utils": "workspace:^" }, "peerDependencies": { - "@solidjs/web": "^2.0.0-beta.10", - "solid-js": "^2.0.0-beta.10" + "@solidjs/web": "^2.0.0-beta.14", + "solid-js": "^2.0.0-beta.14" }, "typesVersions": {}, "devDependencies": { - "@solidjs/web": "2.0.0-beta.10", - "solid-js": "2.0.0-beta.10" + "@solidjs/web": "2.0.0-beta.14", + "solid-js": "2.0.0-beta.14" } } diff --git a/packages/cursor/src/index.ts b/packages/cursor/src/index.ts index 1f35c6474..e290589f3 100644 --- a/packages/cursor/src/index.ts +++ b/packages/cursor/src/index.ts @@ -1,47 +1,9 @@ import { type Accessor, createEffect, createSignal } from "solid-js"; import { isServer } from "@solidjs/web"; import { access, noop, type FalsyValue, type MaybeAccessor } from "@solid-primitives/utils"; +import { type CursorProperty } from "./types.js"; -export type CursorProperty = - | "-moz-grab" - | "-webkit-grab" - | "alias" - | "all-scroll" - | "auto" - | "cell" - | "col-resize" - | "context-menu" - | "copy" - | "crosshair" - | "default" - | "e-resize" - | "ew-resize" - | "grab" - | "grabbing" - | "help" - | "move" - | "n-resize" - | "ne-resize" - | "nesw-resize" - | "no-drop" - | "none" - | "not-allowed" - | "ns-resize" - | "nw-resize" - | "nwse-resize" - | "pointer" - | "progress" - | "row-resize" - | "s-resize" - | "se-resize" - | "sw-resize" - | "text" - | "vertical-text" - | "w-resize" - | "wait" - | "zoom-in" - | "zoom-out" - | (string & {}); +export type { CursorProperty }; /** * Set selected {@link cursor} to body element styles immediately. @@ -79,9 +41,14 @@ export function makeBodyCursor(cursor: CursorProperty): VoidFunction { */ export function makeElementCursor(target: HTMLElement, cursor: CursorProperty): VoidFunction { if (isServer) return noop; - const overwritten = target.style.cursor; + const prevValue = target.style.getPropertyValue("cursor"); + const prevPriority = target.style.getPropertyPriority("cursor"); target.style.setProperty("cursor", cursor, "important"); - return () => (target.style.cursor = overwritten); + return () => { + prevValue + ? target.style.setProperty("cursor", prevValue, prevPriority) + : target.style.removeProperty("cursor"); + }; } /** @@ -116,9 +83,14 @@ export function createElementCursor( const apply = ({ el, cursorValue }: State) => { if (!el) return; - const overwritten = el.style.cursor; + const prevValue = el.style.getPropertyValue("cursor"); + const prevPriority = el.style.getPropertyPriority("cursor"); el.style.setProperty("cursor", cursorValue, "important"); - return () => (el.style.cursor = overwritten); + return () => { + prevValue + ? el.style.setProperty("cursor", prevValue, prevPriority) + : el.style.removeProperty("cursor"); + }; }; createEffect(compute, apply); @@ -144,9 +116,14 @@ export function createBodyCursor(cursor: Accessor): createEffect(cursor, cursorValue => { if (!cursorValue) return; - const overwritten = document.body.style.cursor; + const prevValue = document.body.style.getPropertyValue("cursor"); + const prevPriority = document.body.style.getPropertyPriority("cursor"); document.body.style.setProperty("cursor", cursorValue, "important"); - return () => (document.body.style.cursor = overwritten); + return () => { + prevValue + ? document.body.style.setProperty("cursor", prevValue, prevPriority) + : document.body.style.removeProperty("cursor"); + }; }); } @@ -190,7 +167,10 @@ export function createDragCursor( createEffect( () => access(target), el => { - if (!el) return; + if (!el) { + setDragging(false); + return; + } const onDown = () => setDragging(true); const onUp = () => setDragging(false); el.addEventListener("pointerdown", onDown); diff --git a/packages/cursor/src/types.ts b/packages/cursor/src/types.ts new file mode 100644 index 000000000..56e727df9 --- /dev/null +++ b/packages/cursor/src/types.ts @@ -0,0 +1,40 @@ +export type CursorProperty = + | "-moz-grab" + | "-webkit-grab" + | "alias" + | "all-scroll" + | "auto" + | "cell" + | "col-resize" + | "context-menu" + | "copy" + | "crosshair" + | "default" + | "e-resize" + | "ew-resize" + | "grab" + | "grabbing" + | "help" + | "move" + | "n-resize" + | "ne-resize" + | "nesw-resize" + | "no-drop" + | "none" + | "not-allowed" + | "ns-resize" + | "nw-resize" + | "nwse-resize" + | "pointer" + | "progress" + | "row-resize" + | "s-resize" + | "se-resize" + | "sw-resize" + | "text" + | "vertical-text" + | "w-resize" + | "wait" + | "zoom-in" + | "zoom-out" + | (string & {}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de7dcf973..c274838a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,11 +234,11 @@ importers: version: link:../utils devDependencies: '@solidjs/web': - specifier: 2.0.0-beta.10 - version: 2.0.0-beta.10(solid-js@2.0.0-beta.10) + specifier: 2.0.0-beta.14 + version: 2.0.0-beta.14(solid-js@2.0.0-beta.14) solid-js: - specifier: 2.0.0-beta.10 - version: 2.0.0-beta.10 + specifier: 2.0.0-beta.14 + version: 2.0.0-beta.14 packages/date: dependencies: @@ -2384,113 +2384,8 @@ packages: resolution: {integrity: sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==} engines: {node: '>=20.0'} -<<<<<<< HEAD - '@oxc-project/types@0.127.0': - resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} - - '@parcel/watcher-android-arm64@2.5.1': - resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [android] - - '@parcel/watcher-darwin-arm64@2.5.1': - resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [darwin] - - '@parcel/watcher-darwin-x64@2.5.1': - resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [darwin] - - '@parcel/watcher-freebsd-x64@2.5.1': - resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [freebsd] - - '@parcel/watcher-linux-arm-glibc@2.5.1': - resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@parcel/watcher-linux-arm-musl@2.5.1': - resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - libc: [musl] - - '@parcel/watcher-linux-arm64-glibc@2.5.1': - resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@parcel/watcher-linux-arm64-musl@2.5.1': - resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@parcel/watcher-linux-x64-glibc@2.5.1': - resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@parcel/watcher-linux-x64-musl@2.5.1': - resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@parcel/watcher-wasm@2.3.0': - resolution: {integrity: sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA==} - engines: {node: '>= 10.0.0'} - bundledDependencies: - - napi-wasm - - '@parcel/watcher-wasm@2.5.1': - resolution: {integrity: sha512-RJxlQQLkaMMIuWRozy+z2vEqbaQlCuaCgVZIUCzQLYggY22LZbP5Y1+ia+FD724Ids9e+XIyOLXLrLgQSHIthw==} - engines: {node: '>= 10.0.0'} - bundledDependencies: - - napi-wasm - - '@parcel/watcher-win32-arm64@2.5.1': - resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [win32] - - '@parcel/watcher-win32-ia32@2.5.1': - resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} - engines: {node: '>= 10.0.0'} - cpu: [ia32] - os: [win32] - - '@parcel/watcher-win32-x64@2.5.1': - resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [win32] - - '@parcel/watcher@2.5.1': - resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} - engines: {node: '>= 10.0.0'} -======= '@oxc-project/types@0.130.0': resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} ->>>>>>> next '@peculiar/asn1-schema@2.3.13': resolution: {integrity: sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==}