Solid Iterop v2 (#1925)

* portals all the way

* CI for mobile JS

* whoops

* do install plz

* Solid JSX on mobile

* Cleanup portals + file structure

* wip

* reset explorer changes

* fail

* make it work betterer

* cleanup

* It's not a `useEffect` bug, no way

* Fix `useSubscribeToThemeStore`
This commit is contained in:
Oscar Beaumont 2024-01-10 17:40:18 +08:00 committed by GitHub
parent 4962b1160b
commit 9fc3b8e635
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 443 additions and 100 deletions

View file

@ -25,6 +25,27 @@ concurrency:
cancel-in-progress: true
jobs:
js:
name: JS
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: latest
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: latest
run_install: true
- name: Build mobile JS
run: pnpm mobile export
# Disabled until I can figure out why our app on x86_64 crashes on startup.
# android:
# name: Android

View file

@ -23,6 +23,7 @@ module.exports = function (api) {
}
}
]
]
],
overrides: [{ test: /\.solid.tsx$/, presets: ['solid'] }]
};
};

View file

@ -13,6 +13,7 @@
"android-studio": "open -a '/Applications/Android Studio.app' ./android",
"lint": "eslint src --cache",
"test": "cd ../.. && ./apps/mobile/scripts/run-maestro-tests ios",
"export": "expo export",
"typecheck": "tsc -b"
},
"dependencies": {
@ -30,6 +31,7 @@
"@sd/client": "workspace:*",
"@shopify/flash-list": "1.4.3",
"@tanstack/react-query": "^4.36.1",
"babel-preset-solid": "^1.8.9",
"class-variance-authority": "^0.7.0",
"dayjs": "^1.11.10",
"event-target-polyfill": "^0.0.3",
@ -58,6 +60,7 @@
"react-native-screens": "~3.22.1",
"react-native-svg": "14.1.0",
"react-native-wheel-color-picker": "^1.2.0",
"solid-js": "^1.8.8",
"twrnc": "^3.6.4",
"use-count-up": "^3.0.1",
"use-debounce": "^9.0.4",

View file

@ -1,19 +1,19 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { WithSolid } from '@sd/client';
import { Demo3, demoCtx } from './demo.solid';
import { Demo3, demoCtx, SolidSquare } from './demo.solid';
export function Demo(props: { demo: string }) {
const [count, setCount] = useState(0);
const ctx = demoCtx.useContext();
console.log('FROM REACT 1', ctx());
return (
<div className="bg-green-500 p-2">
<demoCtx.Provider value="set in react">
<>
<button onClick={() => setCount((count) => count + 1)}>Click me</button>
<button onClick={() => setCount((count) => count + 1)} className="border p-1">
Click me
</button>
<div>Hello from React: {count}</div>
<div>{props.demo}</div>
<div>CTX: {ctx()}</div>
@ -22,16 +22,52 @@ export function Demo(props: { demo: string }) {
</>
</demoCtx.Provider>
<WithSolid root={Demo3} demo={count.toString()} />
<SolidSquareManager />
</div>
);
}
function Inner() {
const ctx = demoCtx.useContext();
console.log('FROM REACT 2', ctx());
return null;
}
export function Demo2() {
return null;
}
export function ReactSquare(props: { x: number; y: number }) {
return (
<div
className="absolute z-[999999999] border bg-red-500"
style={{ width: '30px', height: '30px', left: props.x + 'px', top: props.y + 'px' }}
/>
);
}
export function SolidSquareManager() {
const [pos, setPos] = useState({ x: 75, y: 0, enabled: true });
useEffect(() => {
const interval = setInterval(
() =>
setPos((p) => {
if (!p.enabled) return p;
if (p.x > window.innerWidth) return { x: 100, y: 0, enabled: true };
if (p.y > window.innerHeight) return { x: 0, y: 0, enabled: true };
return { x: p.x + 1, y: p.y + 1, enabled: true };
}),
10
);
return () => clearInterval(interval);
});
return (
<>
<button onClick={() => setPos((p) => ({ ...p, enabled: !p.enabled }))}>
Toggle Solid (blue)
</button>
<WithSolid root={SolidSquare} x={pos.x} y={pos.y} />
</>
);
}

View file

@ -3,7 +3,7 @@
import { createSignal } from 'solid-js';
import { createSharedContext, WithReact } from '@sd/client';
import { Demo as ReactDemo, Demo2 as ReactDemo2 } from './demo.react';
import { Demo as ReactDemo, Demo2 as ReactDemo2, ReactSquare } from './demo.react';
export const demoCtx = createSharedContext('the ctx was not set');
@ -23,16 +23,16 @@ export function Demo(props: { demo: string }) {
<div>Hello from Solid: {count()}</div>
<div>CTX: {props.demo}</div>
<Inner />
<WithReact root={ReactDemo} demo={count().toString()} />
<WithReact root={ReactDemo} demo={count().toString()} inner={true} />
<WithReact root={ReactDemo2} />
</demoCtx.Provider>
<ReactSquareManager />
</div>
);
}
function Inner() {
const ctx = demoCtx.useContext();
console.log('FROM SOLID', ctx());
return <div>CTX: {ctx()}</div>;
}
@ -50,3 +50,34 @@ export function Demo3(props: { demo: string }) {
</div>
);
}
export function SolidSquare(props: { x: number; y: number }) {
return (
<div
class="absolute z-[999999999] border bg-blue-500"
style={{ width: '30px', height: '30px', left: props.x + 'px', top: props.y + 'px' }}
/>
);
}
export function ReactSquareManager() {
const [pos, setPos] = createSignal({ x: 100, y: 0, enabled: true });
setInterval(() => {
setPos((p) => {
if (!p.enabled) return p;
if (p.x > window.innerWidth) return { x: 100, y: 0, enabled: true };
if (p.y > window.innerHeight) return { x: 0, y: 0, enabled: true };
return { x: p.x + 1, y: p.y + 1, enabled: true };
});
}, 10);
return (
<>
<button onClick={() => setPos((p) => ({ ...p, enabled: !p.enabled }))}>
Toggle React (red)
</button>
<WithReact root={ReactSquare} x={pos().x} y={pos().y} />
</>
);
}

View file

@ -7,6 +7,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
import { PropsWithChildren, Suspense } from 'react';
import { RouterProvider, RouterProviderProps } from 'react-router-dom';
import {
InteropProviderReact,
P2PContextProvider,
useBridgeSubscription,
useInvalidateQuery,
@ -89,15 +90,17 @@ export function SpacedriveInterfaceRoot({ children }: PropsWithChildren) {
return (
<Suspense>
<BetterErrorBoundary FallbackComponent={ErrorFallback}>
<TooltipProvider>
<P2PContextProvider>
<P2P />
<Devtools />
<WithPrismTheme />
<SpacedropProvider />
{children}
</P2PContextProvider>
</TooltipProvider>
<InteropProviderReact>
<TooltipProvider>
<P2PContextProvider>
<P2P />
<Devtools />
<WithPrismTheme />
<SpacedropProvider />
{children}
</P2PContextProvider>
</TooltipProvider>
</InteropProviderReact>
</BetterErrorBoundary>
</Suspense>
);

View file

@ -2,7 +2,6 @@ import {
createElement,
createContext as createReactContext,
isValidElement,
PropsWithChildren,
JSX as ReactJSX,
useEffect,
useContext as useReactContext,
@ -11,7 +10,6 @@ import {
import {
children,
createContext as createSolidContext,
getOwner,
Owner,
JSX as SolidJSX,
useContext as useSolidContext
@ -109,16 +107,12 @@ export function useWithContextReact(): (elem: () => SolidJSX.Element) => SolidJS
});
}
export function useWithContextSolid(): (elem: ReactJSX.Element) => ReactJSX.Element {
const owner = getOwner()!;
return (elem) => createElement(WithContext, { owner }, elem);
}
function WithContext(props: PropsWithChildren<{ owner: Owner }>) {
const globalCtx = useObserverWithOwner(props.owner, () => {
export function withReactCtx(owner: Owner, elem: ReactJSX.Element) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const globalCtx = useObserverWithOwner(owner, () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
return useSolidContext(solidGlobalContext)();
});
return createElement(reactGlobalContext.Provider, { value: globalCtx }, props.children);
return createElement(reactGlobalContext.Provider, { value: globalCtx }, elem);
}

View file

@ -1,16 +1,6 @@
import { trackDeep } from '@solid-primitives/deep';
import { createEffect, createRoot } from 'solid-js';
import { type Store, type StoreNode } from 'solid-js/store';
import { useObserver } from './useObserver';
export function useSolidStore<T extends object = {}>(store: Store<T>) {
const state = useObserver(() => ({ ...store }));
return new Proxy(state, {
get: (target, prop) => Reflect.get(target, prop),
set: (_, prop, value) => Reflect.set(store, prop, value)
});
}
import { type StoreNode } from 'solid-js/store';
type CreatePersistedMutableOpts<T> = {
onSave?: (value: T) => T;

View file

@ -1,6 +1,8 @@
export * from './interop';
export * from './createPersistedMutable';
export * from './react';
export * from './solid.solid';
export * from './useObserver';
export * from './rq';
export * from './useUniversalQuery';
export * from './useSolidStore';
export { InteropProviderReact } from './portals';
export { createSharedContext } from './context';

View file

@ -0,0 +1,88 @@
import {
createElement,
createContext as createReactContext,
Fragment,
PropsWithChildren,
ReactPortal,
useEffect,
useRef
} from 'react';
import {
children,
createSignal,
createContext as createSolidContext,
For,
Setter,
JSX as SolidJSX,
type Accessor
} from 'solid-js';
import { render } from 'solid-js/web';
import { useObserver } from './useObserver';
export type PortalCtx = {
setSolidPortals: Setter<Portal<SolidJSX.Element>[]>;
setReactPortals: Setter<Portal<ReactPortal>[]>;
};
export type Portal<T> = {
id: string;
portal: T;
};
export const solidPortalCtx = createSolidContext(undefined! as PortalCtx);
export const reactPortalCtx = createReactContext(undefined! as PortalCtx);
// TODO: It would be pog to remove this
export function InteropProviderReact(props: PropsWithChildren) {
const state = useRef({
solidPortals: createSignal([] as Portal<SolidJSX.Element>[]),
reactPortals: createSignal([] as Portal<ReactPortal>[]),
// We only render portals in this so it's never rendered to the DOM
solidRoot: document.createElement('div'),
didFireFirstRender: false
});
useEffect(() => {
// This is to avoid double-rendering SolidJS when used in `React.StrictMode`.
if (!state.current.didFireFirstRender) {
state.current.didFireFirstRender = true;
return;
}
let cleanup = () => {};
cleanup = render(
() =>
For({
get each() {
return state.current.solidPortals[0]();
},
children: (p) => children(() => p.portal) as any
}),
state.current.solidRoot
);
return cleanup;
}, []);
const value: PortalCtx = {
setSolidPortals: state.current.solidPortals[1],
setReactPortals: state.current.reactPortals[1]
};
const portals = createElement(RenderPortals, { portals: state.current.reactPortals[0] });
return createElement(
reactPortalCtx.Provider,
{
value
},
props.children,
portals
);
}
function RenderPortals(props: { portals: Accessor<Portal<ReactPortal>[]> }) {
const portals = useObserver(() => props.portals());
return portals.map((p) => createElement(Fragment, { key: p.id }, p.portal));
}
// TODO: Right now we have React as our app's root so we don't need this but it would be the opposite of `InteropProviderReact`
// export function InteropProviderSolid(props: ParentProps) {}

View file

@ -1,8 +1,19 @@
import { useEffect, useRef } from 'react';
import { JSX as SolidJSX } from 'solid-js';
import { render } from 'solid-js/web';
import {
createElement,
Fragment,
ReactPortal,
useEffect,
useId,
useContext as useReactContext,
useRef
} from 'react';
import { Accessor, createSignal, JSX as SolidJSX } from 'solid-js';
import { createStore } from 'solid-js/store';
import { Portal as SolidPortal } from 'solid-js/web';
import { useWithContextReact } from './context';
import { reactPortalCtx, solidPortalCtx, type Portal } from './portals';
import { useObserver } from './useObserver';
type Props<T> =
| ({
@ -13,18 +24,73 @@ type Props<T> =
};
export function WithSolid<T>(props: Props<T>) {
const portalCtx = useReactContext(reactPortalCtx);
if (!portalCtx) throw new Error('Missing portalCtx in WithSolid');
const id = useId();
const ref = useRef<HTMLDivElement>(null);
const state = useRef({
hasFirstRender: false,
trackedProps: createStore(props),
reactPortals: createSignal([] as Portal<ReactPortal>[])
});
const applyCtx = useWithContextReact();
useEffect(() => {
let cleanup = () => {};
if (ref.current)
cleanup = render(() => {
const { root, ...childProps } = props;
return applyCtx(() => root(childProps as any));
}, ref.current);
return cleanup;
}, [props, applyCtx]);
state.current.trackedProps[1](props);
}, [props]);
return <div ref={ref} />;
useEffect(() => {
if (!ref.current) return;
const hasFirstRender = state.current.hasFirstRender;
if (!hasFirstRender) {
state.current.hasFirstRender = true;
return;
}
portalCtx.setSolidPortals((portals) => [
...portals,
{
id,
portal: (() => {
return SolidPortal({
mount: ref.current!,
get children() {
return solidPortalCtx.Provider({
value: {
setSolidPortals: portalCtx.setSolidPortals,
setReactPortals: state.current.reactPortals[1]
},
get children() {
return applyCtx(() =>
props.root(state.current.trackedProps[0] as T)
);
}
});
}
});
}) as any
}
]);
return () => {
if (!hasFirstRender) return;
portalCtx.setSolidPortals((portals) => portals.filter((p) => p.id !== id));
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // TODO: props.root
return (
<>
<div ref={ref} />
<RenderPortals portals={state.current.reactPortals[0]} />
</>
);
}
function RenderPortals(props: { portals: Accessor<Portal<ReactPortal>[]> }) {
const portals = useObserver(() => props.portals());
return portals.map((p) => createElement(Fragment, { key: p.id }, p.portal));
}

View file

@ -2,10 +2,30 @@
import { trackDeep } from '@solid-primitives/deep';
import { createElement, StrictMode, type FunctionComponent } from 'react';
import ReactDOM from 'react-dom/client';
import { createEffect, onCleanup, splitProps } from 'solid-js';
import { createPortal } from 'react-dom';
import {
children,
createSignal,
createUniqueId,
For,
getOwner,
onCleanup,
onMount,
Owner,
splitProps,
useContext as useSolidContext,
type JSX as SolidJSX
} from 'solid-js';
import { useWithContextSolid } from './context';
import { withReactCtx as withReactContextProvider } from './context';
import { Portal, reactPortalCtx, solidPortalCtx } from './portals';
import { useObserverWithOwner } from './useObserver';
type AllowReactiveScope<T> = T extends object
? {
[P in keyof T]: AllowReactiveScope<T[P]>;
}
: T | (() => T);
type Props<T> =
| {
@ -13,47 +33,71 @@ type Props<T> =
}
| ({
root: FunctionComponent<T>;
} & T);
} & AllowReactiveScope<T>);
export function WithReact<T extends object>(props: Props<T>) {
const portalCtx = useSolidContext(solidPortalCtx);
if (!portalCtx) throw new Error('Missing portalCtx in WithReact');
const [solidPortals, setSolidPortals] = createSignal([] as Portal<SolidJSX.Element>[]);
const id = createUniqueId();
let ref: HTMLDivElement | undefined;
let root: ReactDOM.Root | undefined;
let cleanup: (() => void) | undefined = undefined;
const applyCtx = useWithContextSolid();
const [_, childProps] = splitProps(props, ['root']);
// TODO: Inject all context's
const render = (childProps: any) => {
onMount(() => {
if (!ref) return;
if (!root) {
root = ReactDOM.createRoot(ref);
// The `setTimeout` is to ensure React has time to do the intial render.
// React doesn't like when you unmount it while it's rendering.
cleanup = () => {
setTimeout(() => root?.unmount());
root = undefined;
};
}
root.render(
const elem = createElement(
StrictMode,
null,
createElement(
StrictMode,
null,
applyCtx(createElement(props.root as any, childProps, null))
reactPortalCtx.Provider,
{
value: {
setReactPortals: portalCtx.setReactPortals,
setSolidPortals: setSolidPortals
}
},
createElement(
Wrapper,
{
root: props.root as any,
owner: getOwner()!,
childProps: () => splitProps(props, ['root'])[1]
},
null
)
)
);
};
createEffect(() => {
const trackedProps = trackDeep(childProps);
render({ ...trackedProps });
const portal = createPortal(elem, ref);
portalCtx.setReactPortals((portals) => [
...portals,
{
id,
portal
}
]);
});
onCleanup(() => {
cleanup?.();
cleanup = undefined;
});
onCleanup(() => portalCtx.setReactPortals((portals) => portals.filter((p) => p.id !== id)));
return <div ref={ref} />;
return (
<>
<div ref={ref} />
<For each={solidPortals()}>{(p) => children(() => p.portal) as any}</For>
</>
);
}
function Wrapper<T extends object>(props: {
root: FunctionComponent;
owner: Owner;
childProps: () => T;
}) {
// This is a React component SolidJS reactivity don't matter.
// eslint-disable-next-line solid/reactivity
const childProps = useObserverWithOwner(props.owner, () => trackDeep(props.childProps()));
// eslint-disable-next-line solid/reactivity
return withReactContextProvider(props.owner, createElement(props.root, childProps, null));
}

View file

@ -8,9 +8,12 @@ import { createReaction, createRoot, Owner, runWithOwner } from 'solid-js';
export function useObserver<T>(fn: () => T) {
const [_, setTick] = useState(0);
const state = useRef({
onUpdate: () => {},
onUpdate: () => {
state.current.firedDuringRender = true;
},
// An really ugly workaround for React `StrictMode`'s double firing of `useEffect`.
doneFirstFire: false
doneFirstFire: false,
firedDuringRender: false
});
const reaction = useRef<{ dispose: () => void; track: (fn: () => void) => void }>();
if (!reaction.current) {
@ -21,12 +24,20 @@ export function useObserver<T>(fn: () => T) {
}
useEffect(() => {
if (state.current.firedDuringRender) setTick((t) => t + 1);
// We set this after a `useEffect` to ensure we don't trigger an update prior to mount
// cause that makes React madge.
state.current.onUpdate = () => setTick((t) => t + 1);
state.current.onUpdate = () => {
setTick((t) => t + 1);
};
state.current.doneFirstFire = true;
return () => {
state.current.onUpdate = () => {
state.current.firedDuringRender = true;
};
if (!state.current.doneFirstFire) {
reaction.current?.dispose();
reaction.current = undefined;

View file

@ -0,0 +1,11 @@
import { Store } from 'solid-js/store';
import { useObserver } from './useObserver';
export function useSolidStore<T extends object = {}>(store: Store<T>) {
const state = useObserver(() => ({ ...store }));
return new Proxy(state, {
get: (target, prop) => Reflect.get(target, prop),
set: (_, prop, value) => Reflect.set(store, prop, value)
});
}

View file

@ -1,3 +1,5 @@
import { deepEqual } from 'fast-equals';
import { useRef } from 'react';
import { createMutable } from 'solid-js/store';
import { createPersistedMutable, useObserver, useSolidStore } from '../solid';
@ -18,11 +20,16 @@ export function useThemeStore() {
}
export function useSubscribeToThemeStore(callback: () => void) {
const ref = useRef<typeof themeStore>(themeStore);
useObserver(() => {
// Subscribe to store
const _ = { ...themeStore };
const store = { ...themeStore };
callback();
// Only trigger React if it did in fact change.
if (!deepEqual(store, ref.current)) {
ref.current = store;
callback();
}
});
}

View file

@ -358,6 +358,9 @@ importers:
'@tanstack/react-query':
specifier: ^4.36.1
version: 4.36.1(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)
babel-preset-solid:
specifier: ^1.8.9
version: 1.8.9(@babel/core@7.23.2)
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@ -442,6 +445,9 @@ importers:
react-native-wheel-color-picker:
specifier: ^1.2.0
version: 1.2.0
solid-js:
specifier: ^1.8.8
version: 1.8.8
twrnc:
specifier: ^3.6.4
version: 3.6.4(react-native@0.72.6)
@ -1678,7 +1684,6 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.23.6
dev: true
/@babel/helper-module-imports@7.22.15:
resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
@ -2427,6 +2432,16 @@ packages:
'@babel/core': 7.23.7
'@babel/helper-plugin-utils': 7.22.5
/@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.2):
resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.2
'@babel/helper-plugin-utils': 7.22.5
dev: false
/@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.7):
resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
engines: {node: '>=6.9.0'}
@ -6658,7 +6673,7 @@ packages:
magic-string: 0.27.0
react-docgen-typescript: 2.2.2(typescript@5.3.3)
typescript: 5.3.3
vite: 5.0.10(@types/node@18.17.19)
vite: 5.0.10(less@4.2.0)
/@jridgewell/gen-mapping@0.3.3:
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
@ -9939,7 +9954,7 @@ packages:
magic-string: 0.30.5
rollup: 3.29.4
typescript: 5.3.3
vite: 5.0.10(@types/node@18.17.19)
vite: 5.0.10(less@4.2.0)
transitivePeerDependencies:
- encoding
- supports-color
@ -10292,7 +10307,7 @@ packages:
react: 18.2.0
react-docgen: 6.0.4
react-dom: 18.2.0(react@18.2.0)
vite: 5.0.10(@types/node@18.17.19)
vite: 5.0.10(less@4.2.0)
transitivePeerDependencies:
- '@preact/preset-vite'
- encoding
@ -11694,7 +11709,7 @@ packages:
'@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.2)
magic-string: 0.27.0
react-refresh: 0.14.0
vite: 5.0.10(@types/node@18.17.19)
vite: 5.0.10(less@4.2.0)
transitivePeerDependencies:
- supports-color
@ -12366,6 +12381,19 @@ packages:
validate-html-nesting: 1.2.2
dev: true
/babel-plugin-jsx-dom-expressions@0.37.13(@babel/core@7.23.2):
resolution: {integrity: sha512-oAEMMIgU0h1DmHn4ZDaBBFc08nsVJciLq9pF7g0ZdpeIDKfY4zXjXr8+/oBjKhXG8nyomhnTodPjeG+/ZXcWXQ==}
peerDependencies:
'@babel/core': ^7.20.12
dependencies:
'@babel/core': 7.23.2
'@babel/helper-module-imports': 7.18.6
'@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.2)
'@babel/types': 7.23.6
html-entities: 2.3.3
validate-html-nesting: 1.2.2
dev: false
/babel-plugin-module-resolver@5.0.0:
resolution: {integrity: sha512-g0u+/ChLSJ5+PzYwLwP8Rp8Rcfowz58TJNCe+L/ui4rpzE/mg//JVX0EWBUYoxaextqnwuGHzfGp2hh0PPV25Q==}
engines: {node: '>= 16'}
@ -12589,6 +12617,15 @@ packages:
babel-plugin-jsx-dom-expressions: 0.37.11(@babel/core@7.23.7)
dev: true
/babel-preset-solid@1.8.9(@babel/core@7.23.2):
resolution: {integrity: sha512-1awR1QCoryXtAdnjsrx/eVBTYz+tpHUDOdBXqG9oVV7S0ojf2MV/woR0+8BG+LMXVzIr60oKYzCZ9UZGafxmpg==}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
'@babel/core': 7.23.2
babel-plugin-jsx-dom-expressions: 0.37.13(@babel/core@7.23.2)
dev: false
/bail@2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
dev: false
@ -16544,7 +16581,6 @@ packages:
/html-entities@2.3.3:
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
dev: true
/html-minifier-terser@6.1.0:
resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
@ -24519,7 +24555,6 @@ packages:
/validate-html-nesting@1.2.2:
resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==}
dev: true
/validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
@ -24788,6 +24823,7 @@ packages:
rollup: 4.9.2
optionalDependencies:
fsevents: 2.3.3
dev: true
/vite@5.0.10(less@4.2.0):
resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==}
@ -24823,7 +24859,6 @@ packages:
rollup: 4.9.2
optionalDependencies:
fsevents: 2.3.3
dev: true
/vite@5.0.10(sass@1.69.5):
resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==}