Skip to content

Commit

Permalink
New package: Static Store (#370)
Browse files Browse the repository at this point in the history
  • Loading branch information
thetarnav authored Mar 31, 2023
2 parents a1b88c9 + a5b6375 commit 1e5b0c3
Show file tree
Hide file tree
Showing 38 changed files with 838 additions and 464 deletions.
11 changes: 11 additions & 0 deletions .changeset/big-melons-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@solid-primitives/resize-observer": patch
"@solid-primitives/geolocation": patch
"@solid-primitives/bounds": patch
"@solid-primitives/scroll": patch
"@solid-primitives/audio": patch
"@solid-primitives/media": patch
"@solid-primitives/mouse": patch
---

Use the static-store package for creating reactive objects. Minor overall improvements
5 changes: 5 additions & 0 deletions .changeset/empty-panthers-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solid-primitives/utils": major
---

Move createStaticStore to a separate `static-store` package.
1 change: 1 addition & 0 deletions packages/audio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"solid-heroicons": "^3.1.1"
},
"dependencies": {
"@solid-primitives/static-store": "workspace:^0.0.1",
"@solid-primitives/utils": "workspace:^5.5.1"
},
"peerDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/audio/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Accessor, onMount, onCleanup, createEffect } from "solid-js";
import { isServer } from "solid-js/web";
import { createStaticStore, access, noop } from "@solid-primitives/utils";
import { access, noop } from "@solid-primitives/utils";
import { createStaticStore } from "@solid-primitives/static-store";

// Set of control enums
export enum AudioState {
Expand Down
1 change: 1 addition & 0 deletions packages/bounds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"dependencies": {
"@solid-primitives/event-listener": "workspace:^2.2.9",
"@solid-primitives/resize-observer": "workspace:^2.0.13",
"@solid-primitives/static-store": "workspace:^0.0.1",
"@solid-primitives/utils": "workspace:^5.5.1"
},
"peerDependencies": {
Expand Down
81 changes: 42 additions & 39 deletions packages/bounds/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Accessor, createComputed, on, onCleanup, onMount } from "solid-js";
import { isServer } from "solid-js/web";
import { access, createStaticStore } from "@solid-primitives/utils";
import { createResizeObserver, ResizeHandler } from "@solid-primitives/resize-observer";
import { makeEventListener } from "@solid-primitives/event-listener";
import { createResizeObserver } from "@solid-primitives/resize-observer";
import { createDerivedStaticStore } from "@solid-primitives/static-store";
import { access, FalsyValue } from "@solid-primitives/utils";
import { Accessor, createSignal, onCleanup, onMount, sharedConfig } from "solid-js";
import { isServer } from "solid-js/web";

export type Bounds = {
top: number;
Expand All @@ -13,7 +14,7 @@ export type Bounds = {
height: number;
};

export type NullableBounds = Bounds | typeof NULLED_BOUNDS;
export type NullableBounds = Record<keyof Bounds, number | null>;

export type UpdateGuard = <Args extends unknown[]>(
update: (...args: Args) => void,
Expand All @@ -38,10 +39,10 @@ const NULLED_BOUNDS = {
* @returns object of element's boundingClientRect with enumerable properties
*/
export function getElementBounds(element: Element): Bounds;
export function getElementBounds(element: Element | undefined | null | false): NullableBounds;
export function getElementBounds(element: Element | undefined | null | false): NullableBounds {
export function getElementBounds(element: Element | FalsyValue): NullableBounds;
export function getElementBounds(element: Element | FalsyValue): NullableBounds {
if (isServer || !element) {
return Object.assign({}, NULLED_BOUNDS);
return { ...NULLED_BOUNDS };
}
const rect = element.getBoundingClientRect();
return {
Expand Down Expand Up @@ -80,50 +81,52 @@ export function getElementBounds(element: Element | undefined | null | false): N
* });
* ```
*/

export function createElementBounds(
target: Accessor<Element> | Element,
options?: Options,
): Readonly<Bounds>;
export function createElementBounds(
target: Accessor<Element | undefined | null | false> | Element,
options?: Options,
): Readonly<NullableBounds>;
export function createElementBounds(
target: Accessor<Element | undefined | null | false> | Element,
target: Accessor<Element | FalsyValue> | Element,
{ trackMutation = true, trackResize = true, trackScroll = true }: Options = {},
): Readonly<NullableBounds> {
if (isServer) {
return Object.assign({}, NULLED_BOUNDS);
return NULLED_BOUNDS;
}

const [bounds, setBounds] = createStaticStore(getElementBounds(access(target)));
const updateBounds = () => setBounds(getElementBounds(access(target)));
const updateBoundsOf = (el: Element | undefined | null | false) =>
setBounds(getElementBounds(el));
const isFn = typeof target === "function",
[track, trigger] = createSignal(void 0, { equals: false }) as [() => void, () => void];

if (typeof target === "function") {
onMount(() => updateBoundsOf(target()));
createComputed(on(target, updateBoundsOf, { defer: true }));
let calc: () => NullableBounds;
// during hydration we need to make sure the initial state is the same as the server
if (sharedConfig.context) {
calc = () => NULLED_BOUNDS;
onMount(() => {
calc = () => getElementBounds(access(target));
trigger();
});
}
// a function target might be a jsx ref, so it may not be reactive - retrigger manually on mount
else if (isFn) {
calc = () => getElementBounds(target());
onMount(trigger);
}
// otherwise we can just use the target directly - it will never change
else calc = () => getElementBounds(target);

const bounds = createDerivedStaticStore(() => (track(), calc()));

if (trackResize) {
const resizeHandler: ResizeHandler = (_, el) => updateBoundsOf(el);
createResizeObserver(
typeof target === "function" ? () => target() || [] : target,
typeof trackResize === "function" ? trackResize(resizeHandler) : resizeHandler,
isFn ? () => target() || [] : target,
typeof trackResize === "function" ? trackResize(trigger) : trigger,
);
}

if (trackScroll) {
const scrollHandler =
typeof target === "function"
? (e: Event) => {
const el = target();
if (el && e.target instanceof Node && e.target.contains(el)) updateBoundsOf(el);
}
: (e: Event) => {
if (e.target instanceof Node && e.target.contains(target)) updateBoundsOf(target);
};
const scrollHandler = isFn
? (e: Event) => {
const el = target();
el && e.target instanceof Node && e.target.contains(el) && trigger();
}
: (e: Event) => e.target instanceof Node && e.target.contains(target) && trigger();

makeEventListener(
window,
"scroll",
Expand All @@ -134,14 +137,14 @@ export function createElementBounds(

if (trackMutation) {
const mo = new MutationObserver(
typeof trackMutation === "function" ? trackMutation(updateBounds) : updateBounds,
typeof trackMutation === "function" ? trackMutation(trigger) : trigger,
);
mo.observe(document.body, {
attributeFilter: ["style", "class"],
subtree: true,
childList: true,
});
onCleanup(mo.disconnect.bind(mo));
onCleanup(() => mo.disconnect());
}

return bounds;
Expand Down
1 change: 1 addition & 0 deletions packages/geolocation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"vite-plugin-mkcert": "^1.13.4"
},
"dependencies": {
"@solid-primitives/static-store": "workspace:^0.0.1",
"@solid-primitives/utils": "workspace:^5.5.1"
},
"peerDependencies": {
Expand Down
5 changes: 3 additions & 2 deletions packages/geolocation/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createStaticStore } from "@solid-primitives/static-store";
import { access, MaybeAccessor } from "@solid-primitives/utils";
import type { Resource } from "solid-js";
import { createComputed, createResource, onCleanup } from "solid-js";
import { isServer } from "solid-js/web";
import { createComputed, onCleanup, createResource } from "solid-js";
import { access, MaybeAccessor, createStaticStore } from "@solid-primitives/utils";

const geolocationDefaults: PositionOptions = {
enableHighAccuracy: false,
Expand Down
2 changes: 1 addition & 1 deletion packages/map/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"ReactiveMap",
"ReactiveWeakMap"
],
"category": "Utilities"
"category": "Reactivity"
},
"keywords": [
"map",
Expand Down
1 change: 1 addition & 0 deletions packages/media/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"dependencies": {
"@solid-primitives/event-listener": "workspace:^2.2.9",
"@solid-primitives/rootless": "workspace:^1.3.1",
"@solid-primitives/static-store": "workspace:^0.0.1",
"@solid-primitives/utils": "workspace:^5.5.1"
},
"peerDependencies": {
Expand Down
8 changes: 2 additions & 6 deletions packages/media/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { Accessor } from "solid-js";
import { isServer } from "solid-js/web";
import { makeEventListener } from "@solid-primitives/event-listener";
import {
createHydratableStaticStore,
entries,
noop,
createHydratableSignal,
} from "@solid-primitives/utils";
import { entries, noop, createHydratableSignal } from "@solid-primitives/utils";
import { createHydratableStaticStore } from "@solid-primitives/static-store";
import { createHydratableSingletonRoot } from "@solid-primitives/rootless";

/**
Expand Down
1 change: 1 addition & 0 deletions packages/mouse/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"dependencies": {
"@solid-primitives/event-listener": "workspace:^2.2.9",
"@solid-primitives/rootless": "workspace:^1.3.1",
"@solid-primitives/static-store": "workspace:^0.0.1",
"@solid-primitives/utils": "workspace:^5.5.2"
},
"peerDependencies": {
Expand Down
43 changes: 15 additions & 28 deletions packages/mouse/src/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { createStaticStore, MaybeAccessor, Position } from "@solid-primitives/utils";
import { createHydratableSingletonRoot } from "@solid-primitives/rootless";
import { createDerivedStaticStore, createStaticStore } from "@solid-primitives/static-store";
import { asAccessor, MaybeAccessor, Position } from "@solid-primitives/utils";
import { Accessor, createEffect, createSignal, onMount, sharedConfig } from "solid-js";
import { isServer } from "solid-js/web";
import {
DEFAULT_MOUSE_POSITION,
DEFAULT_RELATIVE_ELEMENT_POSITION,
getPositionToElement,
makeMouseInsideListener,
makeMousePositionListener,
getPositionToElement,
} from "./common";
import {
FollowTouchOptions,
MousePositionInside,
PositionRelativeToElement,
UseTouchOptions,
} from "./types";
import { Accessor, createComputed, createEffect, onMount, sharedConfig } from "solid-js";
import { isServer } from "solid-js/web";

export interface MousePositionOptions extends UseTouchOptions, FollowTouchOptions {
/**
Expand Down Expand Up @@ -125,31 +126,17 @@ export function createPositionToElement(
return fallback;
}

const isFn = typeof element === "function";
const isFn = typeof element === "function",
isHydrating = sharedConfig.context,
getEl = asAccessor(element),
[shouldFallback, setShouldFallback] = createSignal(!!isHydrating, { equals: false });

if (isHydrating || isFn) onMount(() => setShouldFallback(false));

const calcState = (el: Element) => {
return createDerivedStaticStore(() => {
let el!: Element | undefined;
if (shouldFallback() || !(el = getEl())) return fallback;
const { x, y } = pos();
return getPositionToElement(x, y, el);
};

const [state, setState] = createStaticStore<PositionRelativeToElement>(
isFn || sharedConfig.context ? fallback : calcState(element),
);

const getState = isFn
? () => {
const el = element();
return el ? calcState(el) : fallback;
}
: calcState.bind(void 0, element),
updateState = () => setState(getState());

if (sharedConfig.context) {
onMount(() => createComputed(updateState));
} else {
createComputed(updateState);
if (isFn) onMount(updateState);
}

return state;
});
}
2 changes: 1 addition & 1 deletion packages/mouse/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createStaticStore } from "@solid-primitives/utils";
import { createStaticStore } from "@solid-primitives/static-store";
import { createRoot } from "solid-js";
import { describe, expect, it } from "vitest";

Expand Down
1 change: 1 addition & 0 deletions packages/resize-observer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"dependencies": {
"@solid-primitives/event-listener": "workspace:^2.2.9",
"@solid-primitives/rootless": "workspace:^1.3.1",
"@solid-primitives/static-store": "workspace:^0.0.1",
"@solid-primitives/utils": "workspace:^5.5.1"
},
"peerDependencies": {
Expand Down
Loading

0 comments on commit 1e5b0c3

Please sign in to comment.