@wardic/solid 1.0.0-rc.3 → 1.0.0-rc.5
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/browser/feature.js +10 -0
- package/dist/browser/feature.js.map +1 -1
- package/dist/browser/features/input.js +2 -5
- package/dist/browser/features/input.js.map +1 -1
- package/dist/browser/features/scroll.js +21 -0
- package/dist/browser/features/scroll.js.map +1 -0
- package/dist/browser/index.js +4 -2
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/layout.js +1 -1
- package/dist/browser/layout.js.map +1 -1
- package/dist/browser/live-scroll.js +115 -0
- package/dist/browser/live-scroll.js.map +1 -0
- package/dist/browser/live-value.js +10 -97
- package/dist/browser/live-value.js.map +1 -1
- package/dist/browser/reorder.js +14 -10
- package/dist/browser/reorder.js.map +1 -1
- package/dist/browser/runtime.js +20 -2
- package/dist/browser/runtime.js.map +1 -1
- package/dist/browser/unstable/v2/index.js +3 -1
- package/dist/browser/unstable/v2/index.js.map +1 -1
- package/dist/browser/unstable/v2/layout.js +1 -1
- package/dist/browser/unstable/v2/layout.js.map +1 -1
- package/dist/browser/unstable/v2/live-scroll.js +127 -0
- package/dist/browser/unstable/v2/live-scroll.js.map +1 -0
- package/dist/browser/unstable/v2/live-value.js +5 -104
- package/dist/browser/unstable/v2/live-value.js.map +1 -1
- package/dist/browser/unstable/v2/provider.js +13 -7
- package/dist/browser/unstable/v2/provider.js.map +1 -1
- package/dist/server/feature.js +10 -0
- package/dist/server/feature.js.map +1 -1
- package/dist/server/features/input.js +2 -5
- package/dist/server/features/input.js.map +1 -1
- package/dist/server/features/scroll.js +21 -0
- package/dist/server/features/scroll.js.map +1 -0
- package/dist/server/index.js +4 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/layout.js +1 -1
- package/dist/server/layout.js.map +1 -1
- package/dist/server/live-scroll.js +115 -0
- package/dist/server/live-scroll.js.map +1 -0
- package/dist/server/live-value.js +10 -97
- package/dist/server/live-value.js.map +1 -1
- package/dist/server/reorder.js +14 -10
- package/dist/server/reorder.js.map +1 -1
- package/dist/server/runtime.js +20 -2
- package/dist/server/runtime.js.map +1 -1
- package/dist/server/unstable/v2/index.js +3 -1
- package/dist/server/unstable/v2/index.js.map +1 -1
- package/dist/server/unstable/v2/layout.js +1 -1
- package/dist/server/unstable/v2/layout.js.map +1 -1
- package/dist/server/unstable/v2/live-scroll.js +127 -0
- package/dist/server/unstable/v2/live-scroll.js.map +1 -0
- package/dist/server/unstable/v2/live-value.js +5 -104
- package/dist/server/unstable/v2/live-value.js.map +1 -1
- package/dist/server/unstable/v2/provider.js +13 -7
- package/dist/server/unstable/v2/provider.js.map +1 -1
- package/dist/solid/feature.d.ts +5 -0
- package/dist/solid/feature.d.ts.map +1 -1
- package/dist/solid/feature.js +10 -0
- package/dist/solid/feature.js.map +1 -1
- package/dist/solid/features/input.d.ts.map +1 -1
- package/dist/solid/features/input.js +2 -6
- package/dist/solid/features/input.js.map +1 -1
- package/dist/solid/features/scroll.d.ts +16 -0
- package/dist/solid/features/scroll.d.ts.map +1 -0
- package/dist/solid/features/scroll.js +21 -0
- package/dist/solid/features/scroll.js.map +1 -0
- package/dist/solid/index.d.ts +5 -2
- package/dist/solid/index.d.ts.map +1 -1
- package/dist/solid/index.js +4 -2
- package/dist/solid/index.js.map +1 -1
- package/dist/solid/layout.jsx +1 -1
- package/dist/solid/layout.jsx.map +1 -1
- package/dist/solid/live-scroll.d.ts +47 -0
- package/dist/solid/live-scroll.d.ts.map +1 -0
- package/dist/solid/live-scroll.js +84 -0
- package/dist/solid/live-scroll.js.map +1 -0
- package/dist/solid/live-value.d.ts +4 -45
- package/dist/solid/live-value.d.ts.map +1 -1
- package/dist/solid/live-value.js +11 -66
- package/dist/solid/live-value.js.map +1 -1
- package/dist/solid/provider.d.ts +1 -1
- package/dist/solid/provider.d.ts.map +1 -1
- package/dist/solid/reorder.d.ts.map +1 -1
- package/dist/solid/reorder.jsx +11 -6
- package/dist/solid/reorder.jsx.map +1 -1
- package/dist/solid/runtime.d.ts.map +1 -1
- package/dist/solid/runtime.js +20 -2
- package/dist/solid/runtime.js.map +1 -1
- package/dist/solid/unstable/v2/index.d.ts +4 -2
- package/dist/solid/unstable/v2/index.d.ts.map +1 -1
- package/dist/solid/unstable/v2/index.js +3 -1
- package/dist/solid/unstable/v2/index.js.map +1 -1
- package/dist/solid/unstable/v2/layout.jsx +1 -1
- package/dist/solid/unstable/v2/layout.jsx.map +1 -1
- package/dist/solid/unstable/v2/live-scroll.d.ts +47 -0
- package/dist/solid/unstable/v2/live-scroll.d.ts.map +1 -0
- package/dist/solid/unstable/v2/live-scroll.js +93 -0
- package/dist/solid/unstable/v2/live-scroll.js.map +1 -0
- package/dist/solid/unstable/v2/live-value.d.ts +4 -45
- package/dist/solid/unstable/v2/live-value.d.ts.map +1 -1
- package/dist/solid/unstable/v2/live-value.js +5 -70
- package/dist/solid/unstable/v2/live-value.js.map +1 -1
- package/dist/solid/unstable/v2/provider.d.ts +1 -1
- package/dist/solid/unstable/v2/provider.d.ts.map +1 -1
- package/dist/solid/unstable/v2/provider.jsx +14 -7
- package/dist/solid/unstable/v2/provider.jsx.map +1 -1
- package/package.json +4 -4
- package/src/feature.ts +17 -0
- package/src/features/input.ts +2 -10
- package/src/features/scroll.ts +21 -0
- package/src/index.ts +4 -5
- package/src/layout.tsx +1 -1
- package/src/live-scroll.ts +125 -0
- package/src/live-value.ts +11 -106
- package/src/provider.tsx +1 -1
- package/src/reorder.tsx +11 -6
- package/src/runtime.ts +24 -2
- package/src/unstable/v2/index.ts +3 -5
- package/src/unstable/v2/layout.tsx +1 -1
- package/src/unstable/v2/live-scroll.ts +136 -0
- package/src/unstable/v2/live-value.ts +5 -111
- package/src/unstable/v2/provider.tsx +20 -14
package/src/runtime.ts
CHANGED
|
@@ -19,8 +19,11 @@ import * as FlushGate from "@wardic/dom/FlushGate"
|
|
|
19
19
|
import * as ProjectionSystem from "@wardic/dom/ProjectionSystem"
|
|
20
20
|
import * as Report from "@wardic/dom/Report"
|
|
21
21
|
import * as Arr from "effect/Array"
|
|
22
|
+
import * as Bool from "effect/Boolean"
|
|
23
|
+
import * as Cause from "effect/Cause"
|
|
22
24
|
import * as Context from "effect/Context"
|
|
23
25
|
import * as Effect from "effect/Effect"
|
|
26
|
+
import * as Exit from "effect/Exit"
|
|
24
27
|
import { constVoid } from "effect/Function"
|
|
25
28
|
import type * as Fiber from "effect/Fiber"
|
|
26
29
|
import * as Layer from "effect/Layer"
|
|
@@ -28,6 +31,7 @@ import * as ManagedRuntime from "effect/ManagedRuntime"
|
|
|
28
31
|
import * as Option from "effect/Option"
|
|
29
32
|
import * as Predicate from "effect/Predicate"
|
|
30
33
|
import { AtomRegistry } from "effect/reactivity"
|
|
34
|
+
import * as Result from "effect/Result"
|
|
31
35
|
import * as UndefinedOr from "effect/UndefinedOr"
|
|
32
36
|
import type { ServerAppear } from "./appear.ts"
|
|
33
37
|
import { clientRegistry, type FeatureRegistry, type FeatureSection, serverRegistry } from "./feature.ts"
|
|
@@ -46,6 +50,24 @@ import { settingOf as configSettingOf, type WardicConfigValue } from "./wardic-c
|
|
|
46
50
|
*/
|
|
47
51
|
export type WardicEnv = Layer.Layer<WardicServices, never, never>
|
|
48
52
|
|
|
53
|
+
/** What a provider fails with when its `env` waits as it builds: the fix, where Effect's own `AsyncFiberError` names neither. */
|
|
54
|
+
const WAITING_ENV =
|
|
55
|
+
"The WardicProvider's env waits as it builds, and a provider builds it at once, as its first element renders. Build the Layer that waits, such as DevTools.layer(), before the app renders, and give its services in the env: Layer.succeedContext(await Effect.runPromise(Layer.buildWithScope(layer, scope))). See https://wardic.dev/guides/observability."
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `env`, built at once, on the runtime's memo map and Scope as it would be unwrapped. A provider
|
|
59
|
+
* runs its services from a render, so a Layer that waits as it builds, such as `DevTools.layer()`
|
|
60
|
+
* waiting for its connection, fails the build with {@link WAITING_ENV}; any other failure is its own.
|
|
61
|
+
*/
|
|
62
|
+
const builtAtOnce = (env: WardicEnv): WardicEnv =>
|
|
63
|
+
Layer.fromBuild((memoMap, scope) => {
|
|
64
|
+
const exit = Effect.runSyncExit(Layer.buildWithMemoMap(env, memoMap, scope))
|
|
65
|
+
return Bool.match(Option.exists(Result.getSuccess(Exit.findDefect(exit)), Cause.isAsyncFiberError), {
|
|
66
|
+
onTrue: () => Effect.die(new Error(WAITING_ENV)),
|
|
67
|
+
onFalse: () => exit,
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
49
71
|
/** The services a `WardicProvider`'s runtime gives the code run on it: Wardic's Environment, as a union of Effect services. */
|
|
50
72
|
export type WardicServices =
|
|
51
73
|
| Clock.Clock
|
|
@@ -193,7 +215,7 @@ export const clientContext = (
|
|
|
193
215
|
// layout animation starts, and the app's scale correctors.
|
|
194
216
|
const projectionOptions: ProjectionSystem.Options = { reducedMotion: () => build().reducedMotion(), scaleCorrectors }
|
|
195
217
|
const runtime: ProviderRuntime = ManagedRuntime.make(
|
|
196
|
-
Layer.merge(FlushGate.layer({ batch }), Layer.succeed(ProjectionOptions)(projectionOptions)).pipe(Layer.provideMerge(env)),
|
|
218
|
+
Layer.merge(FlushGate.layer({ batch }), Layer.succeed(ProjectionOptions)(projectionOptions)).pipe(Layer.provideMerge(builtAtOnce(env))),
|
|
197
219
|
)
|
|
198
220
|
const features = clientRegistry(runtime)
|
|
199
221
|
// Its Instant changes (#443), timed by its Environment's Refresh frames, found at the first.
|
|
@@ -262,7 +284,7 @@ const settingOf = (setting: ReducedMotion.Config | undefined, provider: () => Re
|
|
|
262
284
|
* Appear animations when it has them.
|
|
263
285
|
*/
|
|
264
286
|
export const serverContext = (env: WardicEnv, appear: ServerAppear | undefined, reducedMotion: () => ReducedMotion.Config | undefined): WardicContextValue => {
|
|
265
|
-
const runtime = ManagedRuntime.make(env)
|
|
287
|
+
const runtime = ManagedRuntime.make(builtAtOnce(env))
|
|
266
288
|
return {
|
|
267
289
|
runtime,
|
|
268
290
|
// A server render's elements take the lean path and never read it: read at each use if one does.
|
package/src/unstable/v2/index.ts
CHANGED
|
@@ -24,11 +24,12 @@ export {
|
|
|
24
24
|
* only those it uses; a Complete element (`wardic.*`, `ward()`) has every one. `colors`: every CSS
|
|
25
25
|
* colour space beyond a hex, `rgb()`, `hsl()` and the colour names. `layout`: layout animations, the
|
|
26
26
|
* props `layout`, `layoutId`, `layoutScroll` and `layoutRoot` (`LayoutGroup` and `Reorder` bring it
|
|
27
|
-
* themselves).
|
|
27
|
+
* themselves). `scroll`: a container's scroll, which `createScroll` brings itself (#518).
|
|
28
28
|
*/
|
|
29
29
|
export type { Feature, FeatureLoad, Features, FeaturesProp } from "../../feature.ts"
|
|
30
30
|
export { colors } from "../../features/colors.ts"
|
|
31
31
|
export { layout } from "../../features/layout.ts"
|
|
32
|
+
export { scroll } from "../../features/scroll.ts"
|
|
32
33
|
/**
|
|
33
34
|
* The gestures (#511, ADR-0067 decision 3), a Feature each: `hover`, `press` (its Keyboard presses
|
|
34
35
|
* included), `focus`, `inView`, `pan` and `drag` (`dragControls` and the release's inertia included).
|
|
@@ -209,16 +210,13 @@ export { createAnimationControls, type WardicAnimationControls, type WardicContr
|
|
|
209
210
|
export {
|
|
210
211
|
createAnimationFrame,
|
|
211
212
|
createFollowValue,
|
|
212
|
-
createScroll,
|
|
213
213
|
createSpring,
|
|
214
214
|
createTime,
|
|
215
215
|
createTransform,
|
|
216
216
|
createVelocity,
|
|
217
217
|
createWillChange,
|
|
218
|
-
type ElementSource,
|
|
219
|
-
type ScrollOptions,
|
|
220
|
-
type ScrollValues,
|
|
221
218
|
} from "./live-value.ts"
|
|
219
|
+
export { createScroll, type ElementSource, type ScrollOptions, type ScrollValues } from "./live-scroll.ts"
|
|
222
220
|
export type { FollowAccessor, FollowValueOptions, Live, LiveSource, SpringAccessor, WillChange } from "../../live-style.ts"
|
|
223
221
|
|
|
224
222
|
/**
|
|
@@ -67,7 +67,7 @@ export function LayoutGroup(props: LayoutGroupProps): JSX.Element {
|
|
|
67
67
|
const id = untrack(() => layoutGroupIdOf(outer, props.id, props.inherit))
|
|
68
68
|
// A server render has no projection system to re-measure (ADR-0021). Not at mount: nodes measure
|
|
69
69
|
// themselves as they register.
|
|
70
|
-
if (
|
|
70
|
+
if (!isServer) {
|
|
71
71
|
const projection = useWardicProjection()
|
|
72
72
|
// Compared before it measures, as Motion's is: an effect never compares (#618).
|
|
73
73
|
const dependency = createMemo(() => props.layoutDependency, CLIENT_ONLY)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createScroll` (#518): in a module of its own, so it brings the `scroll` Feature only to an app that
|
|
3
|
+
* reads a scroll, and on the pages that do (ADR-0067, *Amendments (#548, #518)*).
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import * as LiveValue from "@wardic/core/LiveValue"
|
|
8
|
+
import * as ScrollSource from "@wardic/core/ScrollSource"
|
|
9
|
+
import type * as ScrollOffset from "@wardic/dom/ScrollOffset"
|
|
10
|
+
import * as Arr from "effect/Array"
|
|
11
|
+
import * as Bool from "effect/Boolean"
|
|
12
|
+
import * as Effect from "effect/Effect"
|
|
13
|
+
import * as Option from "effect/Option"
|
|
14
|
+
import * as Predicate from "effect/Predicate"
|
|
15
|
+
import { Atom, AtomRegistry } from "effect/reactivity"
|
|
16
|
+
import * as Rec from "effect/Record"
|
|
17
|
+
import * as Scope from "effect/Scope"
|
|
18
|
+
import * as UndefinedOr from "effect/UndefinedOr"
|
|
19
|
+
import { isServer } from "@solidjs/web"
|
|
20
|
+
import { type Accessor, createEffect, createSignal, onCleanup } from "solid-js"
|
|
21
|
+
import type { Feature } from "../../feature.ts"
|
|
22
|
+
import { scroll } from "../../features/scroll.ts"
|
|
23
|
+
import { serviceOf } from "../../gesture-features.ts"
|
|
24
|
+
import * as OwnerScope from "../../owner-scope.ts"
|
|
25
|
+
import { followAtom } from "./bridge.ts"
|
|
26
|
+
import { CLOSE_FAILED, SERVER_ZERO } from "./live-value.ts"
|
|
27
|
+
import { useBrought, useWardicRuntime } from "./provider.tsx"
|
|
28
|
+
import { CLIENT_ONLY, onSettledClientOnly } from "./solid-reactivity.ts"
|
|
29
|
+
|
|
30
|
+
/** What it brings (ADR-0067 decision 12): the `scroll` Feature, whatever its section has (#518). */
|
|
31
|
+
const SCROLL: ReadonlyArray<Feature> = [scroll]
|
|
32
|
+
|
|
33
|
+
/** An element, or an accessor of one, such as `() => box` for a ref: read once it's there. */
|
|
34
|
+
export type ElementSource = Element | Accessor<Element | undefined>
|
|
35
|
+
|
|
36
|
+
/** `createScroll`'s options, as Motion's `useScroll`'s. */
|
|
37
|
+
export interface ScrollOptions {
|
|
38
|
+
/** The scrolling element, as an accessor of its ref: `() => box`. The page's scroll when left out. */
|
|
39
|
+
readonly container?: ElementSource
|
|
40
|
+
/** An element whose way through the container the progress reads, between the `offset`s. */
|
|
41
|
+
readonly target?: ElementSource
|
|
42
|
+
/**
|
|
43
|
+
* Where the progress runs, as Motion's: each stop pairs an edge of the target with an edge of the
|
|
44
|
+
* container, `["start end", "end start"]`, 0 at the first and 1 at the last. An edge is `start`,
|
|
45
|
+
* `center` or `end`, a fraction, or a length. With a target, `["start start", "end end"]` by default.
|
|
46
|
+
*/
|
|
47
|
+
// The DOM's syntax (`@wardic/dom/ScrollOffset`); with a target and none given, its `All`.
|
|
48
|
+
readonly offset?: ScrollOffset.ScrollOffset
|
|
49
|
+
/** The axis `target` and `offset` read: `"y"` (the default) or `"x"`. */
|
|
50
|
+
readonly axis?: "x" | "y"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** `createScroll`'s values, Motion's four, each an accessor. */
|
|
54
|
+
export interface ScrollValues {
|
|
55
|
+
/** How far it's scrolled across, in pixels. */
|
|
56
|
+
readonly scrollX: Accessor<number>
|
|
57
|
+
/** How far it's scrolled down, in pixels. */
|
|
58
|
+
readonly scrollY: Accessor<number>
|
|
59
|
+
/** Its progress across, from 0 to 1: through its range, or, with a `target` or `offset`, through theirs. */
|
|
60
|
+
readonly scrollXProgress: Accessor<number>
|
|
61
|
+
/** Its progress down, from 0 to 1: through its range, or, with a `target` or `offset`, through theirs. */
|
|
62
|
+
readonly scrollYProgress: Accessor<number>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const SCROLL_KEYS = ["scrollX", "scrollY", "scrollXProgress", "scrollYProgress"] as const
|
|
66
|
+
/** `createScroll`'s elements before the render has settled: none yet. */
|
|
67
|
+
const NOT_YET = { container: Option.none(), target: Option.none() } as const
|
|
68
|
+
const SERVER_SCROLL: ScrollValues = { scrollX: SERVER_ZERO, scrollY: SERVER_ZERO, scrollXProgress: SERVER_ZERO, scrollYProgress: SERVER_ZERO }
|
|
69
|
+
|
|
70
|
+
/** An element source read now: `None` for an accessor whose ref hasn't arrived. */
|
|
71
|
+
const elementOf = (source: ElementSource | undefined): Option.Option<Element | undefined> =>
|
|
72
|
+
Bool.match(Predicate.isFunction(source), {
|
|
73
|
+
onTrue: () => Option.fromUndefinedOr((source as Accessor<Element | undefined>)()),
|
|
74
|
+
onFalse: () => Option.some(source as Element | undefined),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A scroll as values: `scrollX` and `scrollY`, how far it's scrolled, in pixels, and `scrollXProgress`
|
|
79
|
+
* and `scrollYProgress`, from 0 to 1 through its range. The page's, or a `container`'s. With a
|
|
80
|
+
* `target`, the progress is the target's way through, between two `offset`s, on `axis`. Each is set in
|
|
81
|
+
* the scroll's own event, so a value derived from one paints in the frame that shows the scroll. A
|
|
82
|
+
* `container` or `target` given as an accessor of a ref reads 0s until the ref is there. 0s on a
|
|
83
|
+
* server. Motion's `useScroll`.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* let box!: HTMLDivElement
|
|
87
|
+
* const { scrollYProgress } = createScroll({ container: () => box })
|
|
88
|
+
* <div ref={box}>…</div>
|
|
89
|
+
* <w.div style={{ scaleX: scrollYProgress }} />
|
|
90
|
+
* @see https://wardic.dev/reference/solid/live-values/create-scroll
|
|
91
|
+
*/
|
|
92
|
+
// #234. An accessor `container` or `target` is read in an effect, and a change of either follows the
|
|
93
|
+
// new one. Each value carries one Atom for its life.
|
|
94
|
+
export const createScroll = (options: ScrollOptions = {}): ScrollValues => {
|
|
95
|
+
if (isServer) return SERVER_SCROLL
|
|
96
|
+
const runtime = useWardicRuntime()
|
|
97
|
+
const services = useBrought(SCROLL)
|
|
98
|
+
const registry = runtime.runSync(AtomRegistry.AtomRegistry)
|
|
99
|
+
// Its own four Atoms, set from the scroll of the elements there now.
|
|
100
|
+
const outs = Rec.fromIterableWith(SCROLL_KEYS, (key) => [key, Atom.make(0)] as const)
|
|
101
|
+
Arr.forEach(SCROLL_KEYS, (key) => onCleanup(registry.mount(outs[key])))
|
|
102
|
+
// Its elements are read once the render has settled: a ref given as `() => box` is set during the
|
|
103
|
+
// render, after this effect's first compute, and isn't a signal, so nothing would read it again.
|
|
104
|
+
const [settled, setSettled] = createSignal(false)
|
|
105
|
+
onSettledClientOnly(() => {
|
|
106
|
+
setSettled(true)
|
|
107
|
+
})
|
|
108
|
+
createEffect(
|
|
109
|
+
() =>
|
|
110
|
+
Bool.match(settled(), {
|
|
111
|
+
onTrue: () => ({ container: elementOf(options.container), target: elementOf(options.target) }),
|
|
112
|
+
onFalse: () => NOT_YET,
|
|
113
|
+
}),
|
|
114
|
+
({ container, target }) => {
|
|
115
|
+
// A ref not there yet: the values hold until it is.
|
|
116
|
+
if (Bool.or(Option.isNone(container), Option.isNone(target))) return undefined
|
|
117
|
+
const scope = OwnerScope.open(runtime)
|
|
118
|
+
const values = runtime.runSync(
|
|
119
|
+
LiveValue.scroll({
|
|
120
|
+
...UndefinedOr.match(Option.getOrUndefined(container), { onUndefined: () => ({}), onDefined: (element) => ({ container: element }) }),
|
|
121
|
+
...UndefinedOr.match(Option.getOrUndefined(target), { onUndefined: () => ({}), onDefined: (element) => ({ target: element }) }),
|
|
122
|
+
...UndefinedOr.match(options.offset, { onUndefined: () => ({}), onDefined: (offset) => ({ offset }) }),
|
|
123
|
+
...UndefinedOr.match(options.axis, { onUndefined: () => ({}), onDefined: (axis) => ({ axis }) }),
|
|
124
|
+
}).pipe(Effect.provideService(ScrollSource.ScrollSource, serviceOf(services(), ScrollSource.ScrollSource)), Scope.provide(scope)),
|
|
125
|
+
)
|
|
126
|
+
// Each derived Atom is read at once: it tracks the scroll only once it's been read.
|
|
127
|
+
const stops = Arr.map(SCROLL_KEYS, (key) => registry.subscribe(values[key], (v) => registry.set(outs[key], v), { immediate: true }))
|
|
128
|
+
return () => {
|
|
129
|
+
Arr.forEach(stops, (stop) => stop())
|
|
130
|
+
OwnerScope.close(runtime, scope, CLOSE_FAILED)
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
CLIENT_ONLY,
|
|
134
|
+
)
|
|
135
|
+
return Rec.map(outs, (atom) => followAtom(registry, atom)) as unknown as ScrollValues
|
|
136
|
+
}
|
|
@@ -25,18 +25,14 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import type * as InterpolatorRegistry from "@wardic/core/InterpolatorRegistry"
|
|
27
27
|
import * as LiveValue from "@wardic/core/LiveValue"
|
|
28
|
-
import type * as ScrollOffset from "@wardic/dom/ScrollOffset"
|
|
29
|
-
import * as Arr from "effect/Array"
|
|
30
28
|
import * as Bool from "effect/Boolean"
|
|
31
29
|
import * as Effect from "effect/Effect"
|
|
32
|
-
import * as Option from "effect/Option"
|
|
33
30
|
import * as Predicate from "effect/Predicate"
|
|
34
31
|
import { Atom, AtomRegistry } from "effect/reactivity"
|
|
35
|
-
import * as Rec from "effect/Record"
|
|
36
32
|
import * as Scope from "effect/Scope"
|
|
37
33
|
import * as UndefinedOr from "effect/UndefinedOr"
|
|
38
34
|
import { isServer } from "@solidjs/web"
|
|
39
|
-
import { type Accessor, createEffect, createMemo,
|
|
35
|
+
import { type Accessor, createEffect, createMemo, onCleanup, untrack } from "solid-js"
|
|
40
36
|
import {
|
|
41
37
|
atomOf,
|
|
42
38
|
type FollowAccessor,
|
|
@@ -59,7 +55,7 @@ import { eachFrameOf } from "../../animation-frame.ts"
|
|
|
59
55
|
import * as OwnerScope from "../../owner-scope.ts"
|
|
60
56
|
import { withServerInterpolators } from "../../server-interpolators.ts"
|
|
61
57
|
import { followAtom } from "./bridge.ts"
|
|
62
|
-
import { CLIENT_ONLY, CLIENT_ONLY_DEFERRED
|
|
58
|
+
import { CLIENT_ONLY, CLIENT_ONLY_DEFERRED } from "./solid-reactivity.ts"
|
|
63
59
|
import { useWardicRuntime, useWardicSkipAnimations } from "./provider.tsx"
|
|
64
60
|
|
|
65
61
|
/** A Live value read now on a server: an Atom through a server's own registry, an accessor called. */
|
|
@@ -87,7 +83,7 @@ const sourceAtomOf = <A>(source: LiveSource<A>, registry: AtomRegistry.AtomRegis
|
|
|
87
83
|
})
|
|
88
84
|
|
|
89
85
|
/** What a helper's Scope reports when one of its finalizers fails as its owner goes. */
|
|
90
|
-
const CLOSE_FAILED = "wardic.liveValue: a finalizer failed"
|
|
86
|
+
export const CLOSE_FAILED = "wardic.liveValue: a finalizer failed"
|
|
91
87
|
|
|
92
88
|
/** A helper's Atom, made on the provider's runtime in a Scope its owner's disposal closes, as an accessor carrying it. */
|
|
93
89
|
const liveOf = <A, R>(runtime: WardicRuntime, live: Effect.Effect<Atom.Atom<A>, never, R>): Accessor<A> => {
|
|
@@ -264,7 +260,8 @@ export const createTime = (): Accessor<number> => {
|
|
|
264
260
|
return liveOf(useWardicRuntime(), LiveValue.time())
|
|
265
261
|
}
|
|
266
262
|
|
|
267
|
-
|
|
263
|
+
/** A server render's value of a Live helper: 0, and no Atom. Internal. */
|
|
264
|
+
export const SERVER_ZERO: Accessor<number> = () => 0
|
|
268
265
|
|
|
269
266
|
/**
|
|
270
267
|
* A `will-change` for an element's `style`: `"auto"` until the element first animates a transform key,
|
|
@@ -309,106 +306,3 @@ export const createAnimationFrame = (callback: (time: number, delta: number) =>
|
|
|
309
306
|
runtime.runSync(eachFrameOf(runtime, callback).pipe(Scope.provide(scope)))
|
|
310
307
|
}
|
|
311
308
|
|
|
312
|
-
/** An element, or an accessor of one, such as `() => box` for a ref: read once it's there. */
|
|
313
|
-
export type ElementSource = Element | Accessor<Element | undefined>
|
|
314
|
-
|
|
315
|
-
/** `createScroll`'s options, as Motion's `useScroll`'s. */
|
|
316
|
-
export interface ScrollOptions {
|
|
317
|
-
/** The scrolling element, as an accessor of its ref: `() => box`. The page's scroll when left out. */
|
|
318
|
-
readonly container?: ElementSource
|
|
319
|
-
/** An element whose way through the container the progress reads, between the `offset`s. */
|
|
320
|
-
readonly target?: ElementSource
|
|
321
|
-
/**
|
|
322
|
-
* Where the progress runs, as Motion's: each stop pairs an edge of the target with an edge of the
|
|
323
|
-
* container, `["start end", "end start"]`, 0 at the first and 1 at the last. An edge is `start`,
|
|
324
|
-
* `center` or `end`, a fraction, or a length. With a target, `["start start", "end end"]` by default.
|
|
325
|
-
*/
|
|
326
|
-
// The DOM's syntax (`@wardic/dom/ScrollOffset`); with a target and none given, its `All`.
|
|
327
|
-
readonly offset?: ScrollOffset.ScrollOffset
|
|
328
|
-
/** The axis `target` and `offset` read: `"y"` (the default) or `"x"`. */
|
|
329
|
-
readonly axis?: "x" | "y"
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/** `createScroll`'s values, Motion's four, each an accessor. */
|
|
333
|
-
export interface ScrollValues {
|
|
334
|
-
/** How far it's scrolled across, in pixels. */
|
|
335
|
-
readonly scrollX: Accessor<number>
|
|
336
|
-
/** How far it's scrolled down, in pixels. */
|
|
337
|
-
readonly scrollY: Accessor<number>
|
|
338
|
-
/** Its progress across, from 0 to 1: through its range, or, with a `target` or `offset`, through theirs. */
|
|
339
|
-
readonly scrollXProgress: Accessor<number>
|
|
340
|
-
/** Its progress down, from 0 to 1: through its range, or, with a `target` or `offset`, through theirs. */
|
|
341
|
-
readonly scrollYProgress: Accessor<number>
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const SCROLL_KEYS = ["scrollX", "scrollY", "scrollXProgress", "scrollYProgress"] as const
|
|
345
|
-
/** `createScroll`'s elements before the render has settled: none yet. */
|
|
346
|
-
const NOT_YET = { container: Option.none(), target: Option.none() } as const
|
|
347
|
-
const SERVER_SCROLL: ScrollValues = { scrollX: SERVER_ZERO, scrollY: SERVER_ZERO, scrollXProgress: SERVER_ZERO, scrollYProgress: SERVER_ZERO }
|
|
348
|
-
|
|
349
|
-
/** An element source read now: `None` for an accessor whose ref hasn't arrived. */
|
|
350
|
-
const elementOf = (source: ElementSource | undefined): Option.Option<Element | undefined> =>
|
|
351
|
-
Bool.match(Predicate.isFunction(source), {
|
|
352
|
-
onTrue: () => Option.fromUndefinedOr((source as Accessor<Element | undefined>)()),
|
|
353
|
-
onFalse: () => Option.some(source as Element | undefined),
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* A scroll as values: `scrollX` and `scrollY`, how far it's scrolled, in pixels, and `scrollXProgress`
|
|
358
|
-
* and `scrollYProgress`, from 0 to 1 through its range. The page's, or a `container`'s. With a
|
|
359
|
-
* `target`, the progress is the target's way through, between two `offset`s, on `axis`. Each is set in
|
|
360
|
-
* the scroll's own event, so a value derived from one paints in the frame that shows the scroll. A
|
|
361
|
-
* `container` or `target` given as an accessor of a ref reads 0s until the ref is there. 0s on a
|
|
362
|
-
* server. Motion's `useScroll`.
|
|
363
|
-
*
|
|
364
|
-
* @example
|
|
365
|
-
* let box!: HTMLDivElement
|
|
366
|
-
* const { scrollYProgress } = createScroll({ container: () => box })
|
|
367
|
-
* <div ref={box}>…</div>
|
|
368
|
-
* <w.div style={{ scaleX: scrollYProgress }} />
|
|
369
|
-
* @see https://wardic.dev/reference/solid/live-values/create-scroll
|
|
370
|
-
*/
|
|
371
|
-
// #234. An accessor `container` or `target` is read in an effect, and a change of either follows the
|
|
372
|
-
// new one. Each value carries one Atom for its life.
|
|
373
|
-
export const createScroll = (options: ScrollOptions = {}): ScrollValues => {
|
|
374
|
-
if (isServer) return SERVER_SCROLL
|
|
375
|
-
const runtime = useWardicRuntime()
|
|
376
|
-
const registry = runtime.runSync(AtomRegistry.AtomRegistry)
|
|
377
|
-
// Its own four Atoms, set from the scroll of the elements there now.
|
|
378
|
-
const outs = Rec.fromIterableWith(SCROLL_KEYS, (key) => [key, Atom.make(0)] as const)
|
|
379
|
-
Arr.forEach(SCROLL_KEYS, (key) => onCleanup(registry.mount(outs[key])))
|
|
380
|
-
// Its elements are read once the render has settled: a ref given as `() => box` is set during the
|
|
381
|
-
// render, after this effect's first compute, and isn't a signal, so nothing would read it again.
|
|
382
|
-
const [settled, setSettled] = createSignal(false)
|
|
383
|
-
onSettledClientOnly(() => {
|
|
384
|
-
setSettled(true)
|
|
385
|
-
})
|
|
386
|
-
createEffect(
|
|
387
|
-
() =>
|
|
388
|
-
Bool.match(settled(), {
|
|
389
|
-
onTrue: () => ({ container: elementOf(options.container), target: elementOf(options.target) }),
|
|
390
|
-
onFalse: () => NOT_YET,
|
|
391
|
-
}),
|
|
392
|
-
({ container, target }) => {
|
|
393
|
-
// A ref not there yet: the values hold until it is.
|
|
394
|
-
if (Bool.or(Option.isNone(container), Option.isNone(target))) return undefined
|
|
395
|
-
const scope = OwnerScope.open(runtime)
|
|
396
|
-
const values = runtime.runSync(
|
|
397
|
-
LiveValue.scroll({
|
|
398
|
-
...UndefinedOr.match(Option.getOrUndefined(container), { onUndefined: () => ({}), onDefined: (element) => ({ container: element }) }),
|
|
399
|
-
...UndefinedOr.match(Option.getOrUndefined(target), { onUndefined: () => ({}), onDefined: (element) => ({ target: element }) }),
|
|
400
|
-
...UndefinedOr.match(options.offset, { onUndefined: () => ({}), onDefined: (offset) => ({ offset }) }),
|
|
401
|
-
...UndefinedOr.match(options.axis, { onUndefined: () => ({}), onDefined: (axis) => ({ axis }) }),
|
|
402
|
-
}).pipe(Scope.provide(scope)),
|
|
403
|
-
)
|
|
404
|
-
// Each derived Atom is read at once: it tracks the scroll only once it's been read.
|
|
405
|
-
const stops = Arr.map(SCROLL_KEYS, (key) => registry.subscribe(values[key], (v) => registry.set(outs[key], v), { immediate: true }))
|
|
406
|
-
return () => {
|
|
407
|
-
Arr.forEach(stops, (stop) => stop())
|
|
408
|
-
OwnerScope.close(runtime, scope, CLOSE_FAILED)
|
|
409
|
-
}
|
|
410
|
-
},
|
|
411
|
-
CLIENT_ONLY,
|
|
412
|
-
)
|
|
413
|
-
return Rec.map(outs, (atom) => followAtom(registry, atom)) as unknown as ScrollValues
|
|
414
|
-
}
|
|
@@ -77,6 +77,23 @@ const WardicContext = createContext<WardicContextValue | null>(null)
|
|
|
77
77
|
/** The nearest provider's value, `undefined` outside one. */
|
|
78
78
|
const contextValue = (): WardicContextValue | undefined => Option.getOrUndefined(Option.fromNullishOr(useContext(WardicContext)))
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* A provider's context: a server render's, inert, or the client's. Decided by a plain `if`: a client
|
|
82
|
+
* build folds `isServer` and drops the server render's code, where `Bool.match` kept it (#518: 8.4 KB
|
|
83
|
+
* gzipped in every Solid 2 app). `env` and `appear` are read once, as the provider's docs say.
|
|
84
|
+
*/
|
|
85
|
+
const contextOf = (given: Parameters<typeof WardicProvider>[0]): WardicContextValue => {
|
|
86
|
+
if (isServer) return serverContext(given.env, serverAppearOf(given.appear, given.reducedMotion, given.nonce), () => untrack(() => given.reducedMotion))
|
|
87
|
+
return clientContext(
|
|
88
|
+
given.env,
|
|
89
|
+
applyAndFlush,
|
|
90
|
+
() => untrack(() => given.reducedMotion),
|
|
91
|
+
// The provider's setting, tracked too: `createReducedMotionConfig` reads it where it tracks (#584).
|
|
92
|
+
() => given.reducedMotion,
|
|
93
|
+
untrack(() => given.scaleCorrectors),
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
80
97
|
/**
|
|
81
98
|
* Puts Wardic in your app: every Wardic element sits under one. Give it the Environment, `DomLive` in
|
|
82
99
|
* a browser, and the Features your elements use.
|
|
@@ -91,7 +108,7 @@ const contextValue = (): WardicContextValue | undefined => Option.getOrUndefined
|
|
|
91
108
|
// unmount, inert during a server render (ADR-0021). reducedMotion is read as each animation starts
|
|
92
109
|
// (ADR-0029), a WardicConfig's over it (#434); appear (ADR-0044, #585) on the server once.
|
|
93
110
|
export function WardicProvider(props: {
|
|
94
|
-
/** The Environment Wardic runs in: `DomLive` in a browser, from `@wardic/dom`. Read once. */
|
|
111
|
+
/** The Environment Wardic runs in: `DomLive` in a browser, from `@wardic/dom`. Read once, and built at once: a Layer that waits as it builds fails it. */
|
|
95
112
|
readonly env: WardicEnv
|
|
96
113
|
/**
|
|
97
114
|
* How animations honour the visitor's reduced-motion setting: `"user"` follows it (the default),
|
|
@@ -124,18 +141,7 @@ export function WardicProvider(props: {
|
|
|
124
141
|
readonly strict?: boolean | undefined
|
|
125
142
|
readonly children?: JSX.Element
|
|
126
143
|
}): JSX.Element {
|
|
127
|
-
const value =
|
|
128
|
-
onTrue: () => serverContext(props.env, serverAppearOf(props.appear, props.reducedMotion, props.nonce), () => untrack(() => props.reducedMotion)),
|
|
129
|
-
onFalse: () =>
|
|
130
|
-
clientContext(
|
|
131
|
-
props.env,
|
|
132
|
-
applyAndFlush,
|
|
133
|
-
() => untrack(() => props.reducedMotion),
|
|
134
|
-
// eslint-disable-next-line solid/reactivity -- the provider's setting, tracked too: `createReducedMotionConfig` reads it where it tracks (#584)
|
|
135
|
-
() => props.reducedMotion,
|
|
136
|
-
untrack(() => props.scaleCorrectors),
|
|
137
|
-
),
|
|
138
|
-
})
|
|
144
|
+
const value = contextOf(props)
|
|
139
145
|
// Disposing the runtime closes its Scope: the projection system, then the gate (#132). Its
|
|
140
146
|
// disposal rejects first, ending every `animateLayout` still waiting: their flights won't end (#257).
|
|
141
147
|
let abort = constUndefined as (reason: DOMException) => void
|
|
@@ -156,7 +162,7 @@ export function WardicProvider(props: {
|
|
|
156
162
|
// element mounted after pays none of it. One mounted before then builds it itself, as before. Asked
|
|
157
163
|
// for here, with no computation of the provider's own (an `onSettled`'s would shift a hydration's
|
|
158
164
|
// owners from the server's), and on the client alone.
|
|
159
|
-
if (
|
|
165
|
+
if (!isServer) onCleanup(whenIdle(() => prepareIdle(value, section)))
|
|
160
166
|
return <WardicContext value={value}>{props.children}</WardicContext>
|
|
161
167
|
}
|
|
162
168
|
|