solidjs-motion 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/LICENSE +21 -0
  3. package/README.md +140 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +2627 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/default-values.d.ts +6 -0
  8. package/dist/src/index.d.ts +16 -0
  9. package/dist/src/motion-config.d.ts +14 -0
  10. package/dist/src/motion-proxy.d.ts +103 -0
  11. package/dist/src/presence-context.d.ts +4 -0
  12. package/dist/src/presence.d.ts +95 -0
  13. package/dist/src/primitives/createDrag.d.ts +16 -0
  14. package/dist/src/primitives/createDragControls.d.ts +30 -0
  15. package/dist/src/primitives/createGestures.d.ts +8 -0
  16. package/dist/src/primitives/createInView.d.ts +51 -0
  17. package/dist/src/primitives/createMotion.d.ts +82 -0
  18. package/dist/src/primitives/createPan.d.ts +83 -0
  19. package/dist/src/primitives/createScroll.d.ts +40 -0
  20. package/dist/src/primitives/gesture-state.d.ts +108 -0
  21. package/dist/src/primitives/motion-value.d.ts +111 -0
  22. package/dist/src/primitives/value-registry.d.ts +34 -0
  23. package/dist/src/reduced-motion.d.ts +29 -0
  24. package/dist/src/style.d.ts +79 -0
  25. package/dist/src/types.d.ts +374 -0
  26. package/dist/src/use-motion.d.ts +35 -0
  27. package/dist/src/variants.d.ts +64 -0
  28. package/package.json +78 -0
  29. package/src/default-values.ts +52 -0
  30. package/src/index.ts +60 -0
  31. package/src/motion-config.tsx +37 -0
  32. package/src/motion-proxy.tsx +377 -0
  33. package/src/presence-context.ts +32 -0
  34. package/src/presence.tsx +466 -0
  35. package/src/primitives/createDrag.ts +670 -0
  36. package/src/primitives/createDragControls.ts +101 -0
  37. package/src/primitives/createGestures.ts +145 -0
  38. package/src/primitives/createInView.ts +124 -0
  39. package/src/primitives/createMotion.ts +638 -0
  40. package/src/primitives/createPan.ts +338 -0
  41. package/src/primitives/createScroll.ts +101 -0
  42. package/src/primitives/gesture-state.ts +772 -0
  43. package/src/primitives/motion-value.ts +328 -0
  44. package/src/primitives/value-registry.ts +114 -0
  45. package/src/reduced-motion.ts +51 -0
  46. package/src/style.ts +266 -0
  47. package/src/types.ts +538 -0
  48. package/src/use-motion.tsx +412 -0
  49. package/src/variants.ts +134 -0
@@ -0,0 +1,328 @@
1
+ import {
2
+ cancelFrame,
3
+ frame,
4
+ isMotionValue,
5
+ type MotionValue,
6
+ transform as motionTransform,
7
+ motionValue,
8
+ type SpringOptions,
9
+ springValue,
10
+ } from "motion"
11
+ import { type Accessor, createComputed, createSignal, onCleanup } from "solid-js"
12
+ import type { MotionValueAccessor } from "../types"
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // MotionValue events the engine can fire — kept narrow so TypeScript autocomplete
16
+ // surfaces only the documented surface.
17
+ // ---------------------------------------------------------------------------
18
+
19
+ type MotionValueEvent = "change" | "animationStart" | "animationComplete" | "animationCancel"
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // makeAccessor — wrap a raw motion.MotionValue as a callable hybrid. Invoking
23
+ // `mv()` returns a Solid-tracked read; every MotionValue method (.get, .set,
24
+ // .jump, .on, .getVelocity, etc.) forwards to the underlying value. Both
25
+ // `isMotionValue(mv)` (duck-typed on .getVelocity) and `typeof mv === "function"`
26
+ // are true; createMotion's splitTarget checks isMotionValue first, so the engine
27
+ // treats hybrids as MotionValues.
28
+ // ---------------------------------------------------------------------------
29
+
30
+ function makeAccessor<T>(mv: MotionValue<T>): MotionValueAccessor<T> {
31
+ // Solid signal bridge — kept in sync via `mv.on("change", ...)`.
32
+ const [signal, setSignal] = createSignal<T>(mv.get())
33
+ // Wrap in updater form so Setter accepts T regardless of its shape (T could
34
+ // include Function for callback-like motion values).
35
+ onCleanup(mv.on("change", (v) => setSignal(() => v)))
36
+
37
+ // The callable: invoking returns the tracked signal value.
38
+ const fn = (() => signal()) as MotionValueAccessor<T>
39
+
40
+ return new Proxy(fn, {
41
+ get(target, prop, receiver) {
42
+ // Function intrinsics (call/apply/bind) stay on the function itself so
43
+ // `fn.call(...)` etc. behave normally.
44
+ if (prop === "call" || prop === "apply" || prop === "bind") {
45
+ return Reflect.get(target, prop, receiver)
46
+ }
47
+ // If we ever attach our own properties to `fn`, prefer those.
48
+ if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver)
49
+ // Forward to the MotionValue. Methods are bound so `this` is the MV.
50
+ const value = Reflect.get(mv as object, prop, mv)
51
+ return typeof value === "function" ? value.bind(mv) : value
52
+ },
53
+ has(target, prop) {
54
+ return Reflect.has(target, prop) || prop in (mv as object)
55
+ },
56
+ })
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // createMotionValue — callable-hybrid MotionValue auto-disposed on cleanup.
61
+ // ---------------------------------------------------------------------------
62
+
63
+ /**
64
+ * Create a {@link MotionValueAccessor} bound to the current reactive scope.
65
+ *
66
+ * The returned value has two access patterns:
67
+ *
68
+ * - `mv()` — invoke as a Solid Accessor. Tracks in JSX, `createEffect`,
69
+ * `createMemo`, etc.
70
+ * - `mv.get()` / `mv.set(v)` / `mv.jump(v)` / `mv.on(...)` — the full upstream
71
+ * {@link MotionValue} surface. Matches motion/react idioms.
72
+ *
73
+ * The same value can be passed as a target in
74
+ * `useMotion({ animate: { x: mv } })` (motion engine sees `.getVelocity` via
75
+ * the Proxy and treats it as a motion value) or directly as the target of
76
+ * `animate(mv, 100)`.
77
+ *
78
+ * Auto-destroyed via `onCleanup` when the owner is disposed.
79
+ *
80
+ * @example
81
+ * const x = createMotionValue(0)
82
+ * x.set(100)
83
+ * animate(x, 200, { duration: 0.5 })
84
+ * <p>{x()}</p> // reactive read in JSX
85
+ */
86
+ export function createMotionValue<T>(initial: T): MotionValueAccessor<T> {
87
+ const mv = motionValue(initial)
88
+ const accessor = makeAccessor(mv)
89
+ // Route the cleanup call through the accessor so test-time spies on
90
+ // `accessor.destroy` are invoked (the Proxy forwards to the underlying mv).
91
+ onCleanup(() => accessor.destroy())
92
+ return accessor
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // toSignal — adapt any raw MotionValue (e.g. from motion's `motionValue()`
97
+ // factory) to a Solid Accessor. Useful when interoperating with motion APIs
98
+ // that return raw MotionValues outside our hybrid factories.
99
+ // ---------------------------------------------------------------------------
100
+
101
+ /**
102
+ * Bridge a raw {@link MotionValue} (from motion's `motionValue()` factory or
103
+ * any other motion API that doesn't return our hybrid) to a Solid
104
+ * {@link Accessor}. Seeds with the current value and updates on every
105
+ * `change` event.
106
+ *
107
+ * **You usually don't need this.** Values returned by `createMotionValue`,
108
+ * `createTransform`, `createSpring`, `createTime`, `createVelocity`, and
109
+ * `createTemplate` are already callable — you can do `mv()` directly. Reach
110
+ * for `toSignal` only when you receive a raw MotionValue from an external API.
111
+ *
112
+ * @example
113
+ * import { motionValue } from "motion"
114
+ * const rawMv = motionValue(0)
115
+ * const xSignal = toSignal(rawMv)
116
+ */
117
+ export function toSignal<T>(mv: MotionValue<T>): Accessor<T> {
118
+ const [value, setValue] = createSignal<T>(mv.get())
119
+ onCleanup(mv.on("change", (v) => setValue(() => v)))
120
+ return value
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // createMotionValueEvent — register a listener with automatic cleanup.
125
+ // ---------------------------------------------------------------------------
126
+
127
+ /**
128
+ * Subscribe to a {@link MotionValue} event with automatic cleanup. Convenience
129
+ * wrapper around `mv.on(event, cb)` for parity with motion/react's
130
+ * `useMotionValueEvent`. For per-change reactivity, prefer
131
+ * `createComputed(() => fn(mv()))` since hybrids are directly callable.
132
+ *
133
+ * @example
134
+ * const x = createMotionValue(0)
135
+ * createMotionValueEvent(x, "animationComplete", () => console.log("done"))
136
+ */
137
+ export function createMotionValueEvent<T>(
138
+ mv: MotionValue<T>,
139
+ event: MotionValueEvent,
140
+ callback: (latest: T) => void,
141
+ ): void {
142
+ onCleanup(mv.on(event, callback))
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Shared helpers
147
+ // ---------------------------------------------------------------------------
148
+
149
+ function readInputValue<T>(input: MotionValue<T> | Accessor<T>): T {
150
+ if (isMotionValue(input)) return (input as MotionValue<T>).get()
151
+ return (input as Accessor<T>)()
152
+ }
153
+
154
+ function subscribeInput<T>(
155
+ input: MotionValue<T> | Accessor<T>,
156
+ onChange: (value: T) => void,
157
+ ): void {
158
+ if (isMotionValue(input)) {
159
+ onCleanup((input as MotionValue<T>).on("change", onChange))
160
+ } else {
161
+ createComputed(() => onChange((input as Accessor<T>)()))
162
+ }
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // createTransform — interpolate one MotionValue/Accessor through a range.
167
+ // Returns a MotionValueAccessor so callable behavior is preserved end-to-end.
168
+ // ---------------------------------------------------------------------------
169
+
170
+ type TransformOptions = NonNullable<Parameters<typeof motionTransform>[2]>
171
+
172
+ /**
173
+ * Create a {@link MotionValueAccessor} that maps an input through a range/
174
+ * output pair. Mirrors motion/react's `useTransform`. The input can be a
175
+ * MotionValue, our hybrid, or any Solid Accessor; the output composes with
176
+ * `animate()`, `useMotion`'s targets, and JSX reactivity.
177
+ *
178
+ * @example
179
+ * const { scrollY } = createScroll()
180
+ * const opacity = createTransform(scrollY, [0, 200], [1, 0])
181
+ * <div style={{ opacity: opacity() }}>...</div>
182
+ */
183
+ export function createTransform<I extends number, O>(
184
+ input: MotionValue<I> | Accessor<I>,
185
+ inputRange: I[],
186
+ outputRange: O[],
187
+ options?: TransformOptions,
188
+ ): MotionValueAccessor<O> {
189
+ const mapper = motionTransform(inputRange, outputRange, options)
190
+ const mv = motionValue(mapper(readInputValue(input)))
191
+ onCleanup(() => mv.destroy())
192
+ subscribeInput(input, (v) => mv.set(mapper(v)))
193
+ return makeAccessor(mv)
194
+ }
195
+
196
+ // ---------------------------------------------------------------------------
197
+ // createSpring — produce a MotionValueAccessor that spring-tracks an input.
198
+ // ---------------------------------------------------------------------------
199
+
200
+ /**
201
+ * Spring-smoothed mirror of a numeric input. Returns a
202
+ * {@link MotionValueAccessor} that tracks the source with physics-based easing.
203
+ *
204
+ * @example
205
+ * const x = createMotionValue(0)
206
+ * const smoothX = createSpring(x, { stiffness: 100, damping: 20 })
207
+ */
208
+ export function createSpring(
209
+ source: MotionValue<number> | Accessor<number>,
210
+ options?: SpringOptions,
211
+ ): MotionValueAccessor<number> {
212
+ if (isMotionValue(source)) {
213
+ const mv = springValue(source as MotionValue<number>, options)
214
+ onCleanup(() => mv.destroy())
215
+ return makeAccessor(mv)
216
+ }
217
+ // Accessor input — bridge through an intermediate MotionValue that mirrors it.
218
+ const bridge = motionValue((source as Accessor<number>)())
219
+ onCleanup(() => bridge.destroy())
220
+ createComputed(() => bridge.set((source as Accessor<number>)()))
221
+ const mv = springValue(bridge, options)
222
+ onCleanup(() => mv.destroy())
223
+ return makeAccessor(mv)
224
+ }
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // createTime — MotionValueAccessor that advances each frame with elapsed ms.
228
+ // ---------------------------------------------------------------------------
229
+
230
+ /**
231
+ * {@link MotionValueAccessor} that advances every animation frame, holding
232
+ * the milliseconds elapsed since this primitive was called. Driver for
233
+ * time-based animations and {@link createTransform}-derived values.
234
+ *
235
+ * @example
236
+ * const t = createTime()
237
+ * const wobble = createTransform(t, [0, 1000, 2000], [0, 10, 0])
238
+ */
239
+ export function createTime(): MotionValueAccessor<number> {
240
+ const mv = motionValue(0)
241
+ onCleanup(() => mv.destroy())
242
+ const startedAt = performance.now()
243
+ const tick = () => mv.set(performance.now() - startedAt)
244
+ frame.update(tick, true)
245
+ onCleanup(() => cancelFrame(tick))
246
+ return makeAccessor(mv)
247
+ }
248
+
249
+ // ---------------------------------------------------------------------------
250
+ // createVelocity — MotionValueAccessor mirroring an input's instantaneous velocity.
251
+ // ---------------------------------------------------------------------------
252
+
253
+ /**
254
+ * {@link MotionValueAccessor} reporting the velocity of a source motion value.
255
+ * Updated whenever the source changes.
256
+ *
257
+ * @example
258
+ * const x = createMotionValue(0)
259
+ * const xVelocity = createVelocity(x)
260
+ */
261
+ export function createVelocity(source: MotionValue<number>): MotionValueAccessor<number> {
262
+ const mv = motionValue(source.getVelocity())
263
+ onCleanup(() => mv.destroy())
264
+ onCleanup(source.on("change", () => mv.set(source.getVelocity())))
265
+ return makeAccessor(mv)
266
+ }
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // createTemplate — tagged template producing a MotionValueAccessor<string>.
270
+ // ---------------------------------------------------------------------------
271
+
272
+ // biome-ignore lint/suspicious/noExplicitAny: MotionValue is invariant in T; `any` lets the template accept MotionValues of any value type.
273
+ type TemplateInput = MotionValue<any> | Accessor<unknown> | string | number
274
+
275
+ /**
276
+ * Tagged template producing a {@link MotionValueAccessor}\<string\>.
277
+ * Interpolated {@link MotionValue}s, hybrids, and Solid Accessors recompute
278
+ * the output string on change; primitives and static strings are baked in.
279
+ *
280
+ * @example
281
+ * const x = createMotionValue(0)
282
+ * const y = createMotionValue(0)
283
+ * const transform = createTemplate`translate(${x}px, ${y}px) scale(1.1)`
284
+ * <div style={{ transform: transform() }} />
285
+ */
286
+ export function createTemplate(
287
+ strings: TemplateStringsArray,
288
+ ...values: TemplateInput[]
289
+ ): MotionValueAccessor<string> {
290
+ const compute = (): string => {
291
+ let out = ""
292
+ for (let i = 0; i < strings.length; i++) {
293
+ out += strings[i]
294
+ if (i < values.length) {
295
+ const v = values[i]
296
+ if (isMotionValue(v)) {
297
+ out += String((v as MotionValue<unknown>).get())
298
+ } else if (typeof v === "function") {
299
+ out += String((v as Accessor<unknown>)())
300
+ } else {
301
+ out += String(v)
302
+ }
303
+ }
304
+ }
305
+ return out
306
+ }
307
+
308
+ const mv = motionValue(compute())
309
+ onCleanup(() => mv.destroy())
310
+
311
+ for (const v of values) {
312
+ if (isMotionValue(v)) {
313
+ onCleanup((v as MotionValue<unknown>).on("change", () => mv.set(compute())))
314
+ }
315
+ }
316
+
317
+ const hasAccessor = values.some((v) => typeof v === "function" && !isMotionValue(v))
318
+ if (hasAccessor) {
319
+ createComputed(() => {
320
+ for (const v of values) {
321
+ if (typeof v === "function" && !isMotionValue(v)) (v as Accessor<unknown>)()
322
+ }
323
+ mv.set(compute())
324
+ })
325
+ }
326
+
327
+ return makeAccessor(mv)
328
+ }
@@ -0,0 +1,114 @@
1
+ import { type MotionValue, motionValue } from "motion"
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Per-element value registry.
5
+ //
6
+ // One `ValueRegistry` per element managed by `createMotion`. It maps style /
7
+ // transform-shortcut keys (`scale`, `y`, `opacity`, etc.) to the
8
+ // `MotionValue` that authoritatively drives the corresponding CSS or
9
+ // transform component for that element.
10
+ //
11
+ // This is the motion-react `visualElement.values` shape, slimmed down. It
12
+ // exists to unify two write paths that currently disagree about who owns a
13
+ // CSS key on the element:
14
+ //
15
+ // 1. **User-provided MVs in `style`** — `<motion.div style={{ scale: mv }}>`.
16
+ // mv is the source of truth for `scale`; subscribing to it and writing
17
+ // `el.style.transform` is the only way the new value reaches the DOM.
18
+ // 2. **animate-target writes** — `useMotion({ animate: { scale: 1.5 } })`.
19
+ // Today these go directly via WAA (`animate(el, target, opts)`). After
20
+ // Stage 3 they will be routed through the registry: if a key has a
21
+ // registered MV, the animation tweens that MV (which writes the DOM
22
+ // via its subscription) instead of writing the element directly.
23
+ //
24
+ // Stage 1 introduces only the data structure. No code reads from or writes
25
+ // to it yet — `createMotion` instantiates an empty registry, attaches a
26
+ // disposal hook, and stops there. Subsequent stages wire it up.
27
+ //
28
+ // Two ownership classes:
29
+ //
30
+ // - **External** — MVs the user created via `createMotionValue` / motion's
31
+ // `motionValue()` and handed us via `style`. The registry tracks them so
32
+ // we know "this key is MV-backed" but does NOT dispose them. The user
33
+ // owns their MV's lifetime.
34
+ //
35
+ // - **Transient** — MVs the registry creates internally (Stage 3) because
36
+ // an animate target referenced a key with no existing MV. The registry
37
+ // owns these and clears them on `dispose()`. motion's MotionValue has no
38
+ // imperative teardown method; releasing references is what lets GC
39
+ // collect them once all subscribers have cleaned up.
40
+ // ---------------------------------------------------------------------------
41
+
42
+ export type ValueRegistry = {
43
+ /** Returns the MV registered for `key`, or `undefined` if none. */
44
+ get(key: string): MotionValue<unknown> | undefined
45
+ /** Has any MV been registered for `key`? */
46
+ has(key: string): boolean
47
+ /**
48
+ * Number of entries currently registered. Used by `createMotion` to decide
49
+ * whether the per-element writer can take a specialized single-key path
50
+ * (size === 1) or needs the general-purpose `applyStaticStyle` walk.
51
+ */
52
+ readonly size: number
53
+ /**
54
+ * Register a user-provided MV. The registry will NOT dispose it on
55
+ * teardown. If a transient MV exists for the key, it is replaced (the
56
+ * external MV becomes the new source of truth).
57
+ */
58
+ setExternal(key: string, mv: MotionValue<unknown>): void
59
+ /**
60
+ * Get the MV for `key`, creating a transient one initialized to
61
+ * `fallback` if absent. Transient MVs are disposed on `dispose()`.
62
+ */
63
+ getOrCreateTransient(key: string, fallback: unknown): MotionValue<unknown>
64
+ /** Iterate every (key, MV) pair currently registered. */
65
+ entries(): IterableIterator<[string, MotionValue<unknown>]>
66
+ /**
67
+ * Drop registry-owned (transient) MVs. External MVs are untouched.
68
+ * Subscription cleanups are owned by whoever called `mv.on(...)`; they
69
+ * tie to the surrounding Solid owner via `onCleanup` in Stage 2+ so we
70
+ * don't unsubscribe imperatively here.
71
+ */
72
+ dispose(): void
73
+ }
74
+
75
+ export function createValueRegistry(): ValueRegistry {
76
+ const values = new Map<string, MotionValue<unknown>>()
77
+ const transient = new Set<MotionValue<unknown>>()
78
+
79
+ return {
80
+ get(key) {
81
+ return values.get(key)
82
+ },
83
+ has(key) {
84
+ return values.has(key)
85
+ },
86
+ setExternal(key, mv) {
87
+ const existing = values.get(key)
88
+ if (existing && transient.has(existing)) {
89
+ // Replacing a transient with an external takes the transient out
90
+ // of the owned-set so dispose() doesn't pretend to manage it.
91
+ transient.delete(existing)
92
+ }
93
+ values.set(key, mv)
94
+ },
95
+ getOrCreateTransient(key, fallback) {
96
+ const existing = values.get(key)
97
+ if (existing) return existing
98
+ const mv = motionValue(fallback) as MotionValue<unknown>
99
+ values.set(key, mv)
100
+ transient.add(mv)
101
+ return mv
102
+ },
103
+ entries() {
104
+ return values.entries()
105
+ },
106
+ get size() {
107
+ return values.size
108
+ },
109
+ dispose() {
110
+ transient.clear()
111
+ values.clear()
112
+ },
113
+ }
114
+ }
@@ -0,0 +1,51 @@
1
+ import { type Accessor, from } from "solid-js"
2
+
3
+ /**
4
+ * A reactive `Accessor<boolean>` tracking the user's
5
+ * `prefers-reduced-motion: reduce` media query.
6
+ *
7
+ * Returns `false` server-side (no `window.matchMedia`). On the client, it seeds
8
+ * with the current match state and updates as the system preference toggles.
9
+ * The matchMedia listener is removed automatically on owner disposal via
10
+ * `from`'s teardown callback.
11
+ *
12
+ * @example
13
+ * const reduced = createReducedMotion()
14
+ * createEffect(() => {
15
+ * if (reduced()) console.log("user prefers reduced motion")
16
+ * })
17
+ */
18
+ export function createReducedMotion(): Accessor<boolean> {
19
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
20
+ return () => false
21
+ }
22
+ const mql = window.matchMedia("(prefers-reduced-motion: reduce)")
23
+ // `from` produces an Accessor<T | undefined>; we seed synchronously inside the
24
+ // producer so the cast to Accessor<boolean> is sound.
25
+ return from<boolean>((set) => {
26
+ set(mql.matches)
27
+ const handler = (e: MediaQueryListEvent) => set(e.matches)
28
+ mql.addEventListener("change", handler)
29
+ return () => mql.removeEventListener("change", handler)
30
+ }) as Accessor<boolean>
31
+ }
32
+
33
+ /**
34
+ * Compute the effective reduced-motion state by combining a {@link MotionConfig}
35
+ * `reducedMotion` setting with the system preference.
36
+ *
37
+ * - `"always"` — forced reduced, regardless of system pref
38
+ * - `"never"` — never reduced, regardless of system pref
39
+ * - `"user"` — respect system pref
40
+ *
41
+ * @example
42
+ * const reduced = shouldReduceMotion("user", createReducedMotion()())
43
+ */
44
+ export function shouldReduceMotion(
45
+ configValue: "always" | "never" | "user",
46
+ systemReduced: boolean,
47
+ ): boolean {
48
+ if (configValue === "always") return true
49
+ if (configValue === "never") return false
50
+ return systemReduced
51
+ }