mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 11:03:27 +00:00
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:
parent
4962b1160b
commit
9fc3b8e635
21
.github/workflows/mobile-ci.yml
vendored
21
.github/workflows/mobile-ci.yml
vendored
|
@ -25,6 +25,27 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
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.
|
# Disabled until I can figure out why our app on x86_64 crashes on startup.
|
||||||
# android:
|
# android:
|
||||||
# name: Android
|
# name: Android
|
||||||
|
|
|
@ -23,6 +23,7 @@ module.exports = function (api) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
],
|
||||||
|
overrides: [{ test: /\.solid.tsx$/, presets: ['solid'] }]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"android-studio": "open -a '/Applications/Android Studio.app' ./android",
|
"android-studio": "open -a '/Applications/Android Studio.app' ./android",
|
||||||
"lint": "eslint src --cache",
|
"lint": "eslint src --cache",
|
||||||
"test": "cd ../.. && ./apps/mobile/scripts/run-maestro-tests ios",
|
"test": "cd ../.. && ./apps/mobile/scripts/run-maestro-tests ios",
|
||||||
|
"export": "expo export",
|
||||||
"typecheck": "tsc -b"
|
"typecheck": "tsc -b"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"@sd/client": "workspace:*",
|
"@sd/client": "workspace:*",
|
||||||
"@shopify/flash-list": "1.4.3",
|
"@shopify/flash-list": "1.4.3",
|
||||||
"@tanstack/react-query": "^4.36.1",
|
"@tanstack/react-query": "^4.36.1",
|
||||||
|
"babel-preset-solid": "^1.8.9",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"event-target-polyfill": "^0.0.3",
|
"event-target-polyfill": "^0.0.3",
|
||||||
|
@ -58,6 +60,7 @@
|
||||||
"react-native-screens": "~3.22.1",
|
"react-native-screens": "~3.22.1",
|
||||||
"react-native-svg": "14.1.0",
|
"react-native-svg": "14.1.0",
|
||||||
"react-native-wheel-color-picker": "^1.2.0",
|
"react-native-wheel-color-picker": "^1.2.0",
|
||||||
|
"solid-js": "^1.8.8",
|
||||||
"twrnc": "^3.6.4",
|
"twrnc": "^3.6.4",
|
||||||
"use-count-up": "^3.0.1",
|
"use-count-up": "^3.0.1",
|
||||||
"use-debounce": "^9.0.4",
|
"use-debounce": "^9.0.4",
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { WithSolid } from '@sd/client';
|
import { WithSolid } from '@sd/client';
|
||||||
|
|
||||||
import { Demo3, demoCtx } from './demo.solid';
|
import { Demo3, demoCtx, SolidSquare } from './demo.solid';
|
||||||
|
|
||||||
export function Demo(props: { demo: string }) {
|
export function Demo(props: { demo: string }) {
|
||||||
const [count, setCount] = useState(0);
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
const ctx = demoCtx.useContext();
|
const ctx = demoCtx.useContext();
|
||||||
console.log('FROM REACT 1', ctx());
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-green-500 p-2">
|
<div className="bg-green-500 p-2">
|
||||||
<demoCtx.Provider value="set in react">
|
<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>Hello from React: {count}</div>
|
||||||
<div>{props.demo}</div>
|
<div>{props.demo}</div>
|
||||||
<div>CTX: {ctx()}</div>
|
<div>CTX: {ctx()}</div>
|
||||||
|
@ -22,16 +22,52 @@ export function Demo(props: { demo: string }) {
|
||||||
</>
|
</>
|
||||||
</demoCtx.Provider>
|
</demoCtx.Provider>
|
||||||
<WithSolid root={Demo3} demo={count.toString()} />
|
<WithSolid root={Demo3} demo={count.toString()} />
|
||||||
|
<SolidSquareManager />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Inner() {
|
function Inner() {
|
||||||
const ctx = demoCtx.useContext();
|
const ctx = demoCtx.useContext();
|
||||||
console.log('FROM REACT 2', ctx());
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Demo2() {
|
export function Demo2() {
|
||||||
return null;
|
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} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { createSignal } from 'solid-js';
|
import { createSignal } from 'solid-js';
|
||||||
import { createSharedContext, WithReact } from '@sd/client';
|
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');
|
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>Hello from Solid: {count()}</div>
|
||||||
<div>CTX: {props.demo}</div>
|
<div>CTX: {props.demo}</div>
|
||||||
<Inner />
|
<Inner />
|
||||||
<WithReact root={ReactDemo} demo={count().toString()} />
|
<WithReact root={ReactDemo} demo={count().toString()} inner={true} />
|
||||||
<WithReact root={ReactDemo2} />
|
<WithReact root={ReactDemo2} />
|
||||||
</demoCtx.Provider>
|
</demoCtx.Provider>
|
||||||
|
<ReactSquareManager />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Inner() {
|
function Inner() {
|
||||||
const ctx = demoCtx.useContext();
|
const ctx = demoCtx.useContext();
|
||||||
console.log('FROM SOLID', ctx());
|
|
||||||
return <div>CTX: {ctx()}</div>;
|
return <div>CTX: {ctx()}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,3 +50,34 @@ export function Demo3(props: { demo: string }) {
|
||||||
</div>
|
</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} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import { PropsWithChildren, Suspense } from 'react';
|
import { PropsWithChildren, Suspense } from 'react';
|
||||||
import { RouterProvider, RouterProviderProps } from 'react-router-dom';
|
import { RouterProvider, RouterProviderProps } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
|
InteropProviderReact,
|
||||||
P2PContextProvider,
|
P2PContextProvider,
|
||||||
useBridgeSubscription,
|
useBridgeSubscription,
|
||||||
useInvalidateQuery,
|
useInvalidateQuery,
|
||||||
|
@ -89,15 +90,17 @@ export function SpacedriveInterfaceRoot({ children }: PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<BetterErrorBoundary FallbackComponent={ErrorFallback}>
|
<BetterErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
<TooltipProvider>
|
<InteropProviderReact>
|
||||||
<P2PContextProvider>
|
<TooltipProvider>
|
||||||
<P2P />
|
<P2PContextProvider>
|
||||||
<Devtools />
|
<P2P />
|
||||||
<WithPrismTheme />
|
<Devtools />
|
||||||
<SpacedropProvider />
|
<WithPrismTheme />
|
||||||
{children}
|
<SpacedropProvider />
|
||||||
</P2PContextProvider>
|
{children}
|
||||||
</TooltipProvider>
|
</P2PContextProvider>
|
||||||
|
</TooltipProvider>
|
||||||
|
</InteropProviderReact>
|
||||||
</BetterErrorBoundary>
|
</BetterErrorBoundary>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,6 @@ import {
|
||||||
createElement,
|
createElement,
|
||||||
createContext as createReactContext,
|
createContext as createReactContext,
|
||||||
isValidElement,
|
isValidElement,
|
||||||
PropsWithChildren,
|
|
||||||
JSX as ReactJSX,
|
JSX as ReactJSX,
|
||||||
useEffect,
|
useEffect,
|
||||||
useContext as useReactContext,
|
useContext as useReactContext,
|
||||||
|
@ -11,7 +10,6 @@ import {
|
||||||
import {
|
import {
|
||||||
children,
|
children,
|
||||||
createContext as createSolidContext,
|
createContext as createSolidContext,
|
||||||
getOwner,
|
|
||||||
Owner,
|
Owner,
|
||||||
JSX as SolidJSX,
|
JSX as SolidJSX,
|
||||||
useContext as useSolidContext
|
useContext as useSolidContext
|
||||||
|
@ -109,16 +107,12 @@ export function useWithContextReact(): (elem: () => SolidJSX.Element) => SolidJS
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWithContextSolid(): (elem: ReactJSX.Element) => ReactJSX.Element {
|
export function withReactCtx(owner: Owner, elem: ReactJSX.Element) {
|
||||||
const owner = getOwner()!;
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
return (elem) => createElement(WithContext, { owner }, elem);
|
const globalCtx = useObserverWithOwner(owner, () => {
|
||||||
}
|
|
||||||
|
|
||||||
function WithContext(props: PropsWithChildren<{ owner: Owner }>) {
|
|
||||||
const globalCtx = useObserverWithOwner(props.owner, () => {
|
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
return useSolidContext(solidGlobalContext)();
|
return useSolidContext(solidGlobalContext)();
|
||||||
});
|
});
|
||||||
|
|
||||||
return createElement(reactGlobalContext.Provider, { value: globalCtx }, props.children);
|
return createElement(reactGlobalContext.Provider, { value: globalCtx }, elem);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,6 @@
|
||||||
import { trackDeep } from '@solid-primitives/deep';
|
import { trackDeep } from '@solid-primitives/deep';
|
||||||
import { createEffect, createRoot } from 'solid-js';
|
import { createEffect, createRoot } from 'solid-js';
|
||||||
import { type Store, type StoreNode } from 'solid-js/store';
|
import { 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)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreatePersistedMutableOpts<T> = {
|
type CreatePersistedMutableOpts<T> = {
|
||||||
onSave?: (value: T) => T;
|
onSave?: (value: T) => T;
|
|
@ -1,6 +1,8 @@
|
||||||
export * from './interop';
|
export * from './createPersistedMutable';
|
||||||
export * from './react';
|
export * from './react';
|
||||||
export * from './solid.solid';
|
export * from './solid.solid';
|
||||||
export * from './useObserver';
|
export * from './useObserver';
|
||||||
export * from './rq';
|
export * from './useUniversalQuery';
|
||||||
|
export * from './useSolidStore';
|
||||||
|
export { InteropProviderReact } from './portals';
|
||||||
export { createSharedContext } from './context';
|
export { createSharedContext } from './context';
|
||||||
|
|
88
packages/client/src/solid/portals.tsx
Normal file
88
packages/client/src/solid/portals.tsx
Normal 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) {}
|
|
@ -1,8 +1,19 @@
|
||||||
import { useEffect, useRef } from 'react';
|
import {
|
||||||
import { JSX as SolidJSX } from 'solid-js';
|
createElement,
|
||||||
import { render } from 'solid-js/web';
|
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 { useWithContextReact } from './context';
|
||||||
|
import { reactPortalCtx, solidPortalCtx, type Portal } from './portals';
|
||||||
|
import { useObserver } from './useObserver';
|
||||||
|
|
||||||
type Props<T> =
|
type Props<T> =
|
||||||
| ({
|
| ({
|
||||||
|
@ -13,18 +24,73 @@ type Props<T> =
|
||||||
};
|
};
|
||||||
|
|
||||||
export function WithSolid<T>(props: 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 ref = useRef<HTMLDivElement>(null);
|
||||||
|
const state = useRef({
|
||||||
|
hasFirstRender: false,
|
||||||
|
trackedProps: createStore(props),
|
||||||
|
reactPortals: createSignal([] as Portal<ReactPortal>[])
|
||||||
|
});
|
||||||
|
|
||||||
const applyCtx = useWithContextReact();
|
const applyCtx = useWithContextReact();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cleanup = () => {};
|
state.current.trackedProps[1](props);
|
||||||
if (ref.current)
|
}, [props]);
|
||||||
cleanup = render(() => {
|
|
||||||
const { root, ...childProps } = props;
|
|
||||||
return applyCtx(() => root(childProps as any));
|
|
||||||
}, ref.current);
|
|
||||||
return cleanup;
|
|
||||||
}, [props, applyCtx]);
|
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,30 @@
|
||||||
|
|
||||||
import { trackDeep } from '@solid-primitives/deep';
|
import { trackDeep } from '@solid-primitives/deep';
|
||||||
import { createElement, StrictMode, type FunctionComponent } from 'react';
|
import { createElement, StrictMode, type FunctionComponent } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import { createPortal } from 'react-dom';
|
||||||
import { createEffect, onCleanup, splitProps } from 'solid-js';
|
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> =
|
type Props<T> =
|
||||||
| {
|
| {
|
||||||
|
@ -13,47 +33,71 @@ type Props<T> =
|
||||||
}
|
}
|
||||||
| ({
|
| ({
|
||||||
root: FunctionComponent<T>;
|
root: FunctionComponent<T>;
|
||||||
} & T);
|
} & AllowReactiveScope<T>);
|
||||||
|
|
||||||
export function WithReact<T extends object>(props: Props<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 ref: HTMLDivElement | undefined;
|
||||||
let root: ReactDOM.Root | undefined;
|
|
||||||
let cleanup: (() => void) | undefined = undefined;
|
|
||||||
|
|
||||||
const applyCtx = useWithContextSolid();
|
onMount(() => {
|
||||||
const [_, childProps] = splitProps(props, ['root']);
|
|
||||||
|
|
||||||
// TODO: Inject all context's
|
|
||||||
const render = (childProps: any) => {
|
|
||||||
if (!ref) return;
|
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(
|
createElement(
|
||||||
StrictMode,
|
reactPortalCtx.Provider,
|
||||||
null,
|
{
|
||||||
applyCtx(createElement(props.root as any, childProps, null))
|
value: {
|
||||||
|
setReactPortals: portalCtx.setReactPortals,
|
||||||
|
setSolidPortals: setSolidPortals
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createElement(
|
||||||
|
Wrapper,
|
||||||
|
{
|
||||||
|
root: props.root as any,
|
||||||
|
owner: getOwner()!,
|
||||||
|
childProps: () => splitProps(props, ['root'])[1]
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
createEffect(() => {
|
const portal = createPortal(elem, ref);
|
||||||
const trackedProps = trackDeep(childProps);
|
portalCtx.setReactPortals((portals) => [
|
||||||
render({ ...trackedProps });
|
...portals,
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
portal
|
||||||
|
}
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => portalCtx.setReactPortals((portals) => portals.filter((p) => p.id !== id)));
|
||||||
cleanup?.();
|
|
||||||
cleanup = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,12 @@ import { createReaction, createRoot, Owner, runWithOwner } from 'solid-js';
|
||||||
export function useObserver<T>(fn: () => T) {
|
export function useObserver<T>(fn: () => T) {
|
||||||
const [_, setTick] = useState(0);
|
const [_, setTick] = useState(0);
|
||||||
const state = useRef({
|
const state = useRef({
|
||||||
onUpdate: () => {},
|
onUpdate: () => {
|
||||||
|
state.current.firedDuringRender = true;
|
||||||
|
},
|
||||||
// An really ugly workaround for React `StrictMode`'s double firing of `useEffect`.
|
// 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 }>();
|
const reaction = useRef<{ dispose: () => void; track: (fn: () => void) => void }>();
|
||||||
if (!reaction.current) {
|
if (!reaction.current) {
|
||||||
|
@ -21,12 +24,20 @@ export function useObserver<T>(fn: () => T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
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
|
// We set this after a `useEffect` to ensure we don't trigger an update prior to mount
|
||||||
// cause that makes React madge.
|
// cause that makes React madge.
|
||||||
state.current.onUpdate = () => setTick((t) => t + 1);
|
state.current.onUpdate = () => {
|
||||||
|
setTick((t) => t + 1);
|
||||||
|
};
|
||||||
state.current.doneFirstFire = true;
|
state.current.doneFirstFire = true;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
state.current.onUpdate = () => {
|
||||||
|
state.current.firedDuringRender = true;
|
||||||
|
};
|
||||||
|
|
||||||
if (!state.current.doneFirstFire) {
|
if (!state.current.doneFirstFire) {
|
||||||
reaction.current?.dispose();
|
reaction.current?.dispose();
|
||||||
reaction.current = undefined;
|
reaction.current = undefined;
|
||||||
|
|
11
packages/client/src/solid/useSolidStore.ts
Normal file
11
packages/client/src/solid/useSolidStore.ts
Normal 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)
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { deepEqual } from 'fast-equals';
|
||||||
|
import { useRef } from 'react';
|
||||||
import { createMutable } from 'solid-js/store';
|
import { createMutable } from 'solid-js/store';
|
||||||
|
|
||||||
import { createPersistedMutable, useObserver, useSolidStore } from '../solid';
|
import { createPersistedMutable, useObserver, useSolidStore } from '../solid';
|
||||||
|
@ -18,11 +20,16 @@ export function useThemeStore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSubscribeToThemeStore(callback: () => void) {
|
export function useSubscribeToThemeStore(callback: () => void) {
|
||||||
|
const ref = useRef<typeof themeStore>(themeStore);
|
||||||
useObserver(() => {
|
useObserver(() => {
|
||||||
// Subscribe to store
|
// 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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -358,6 +358,9 @@ importers:
|
||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: ^4.36.1
|
specifier: ^4.36.1
|
||||||
version: 4.36.1(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0)
|
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:
|
class-variance-authority:
|
||||||
specifier: ^0.7.0
|
specifier: ^0.7.0
|
||||||
version: 0.7.0
|
version: 0.7.0
|
||||||
|
@ -442,6 +445,9 @@ importers:
|
||||||
react-native-wheel-color-picker:
|
react-native-wheel-color-picker:
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
|
solid-js:
|
||||||
|
specifier: ^1.8.8
|
||||||
|
version: 1.8.8
|
||||||
twrnc:
|
twrnc:
|
||||||
specifier: ^3.6.4
|
specifier: ^3.6.4
|
||||||
version: 3.6.4(react-native@0.72.6)
|
version: 3.6.4(react-native@0.72.6)
|
||||||
|
@ -1678,7 +1684,6 @@ packages:
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.23.6
|
'@babel/types': 7.23.6
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@babel/helper-module-imports@7.22.15:
|
/@babel/helper-module-imports@7.22.15:
|
||||||
resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
|
resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
|
||||||
|
@ -2427,6 +2432,16 @@ packages:
|
||||||
'@babel/core': 7.23.7
|
'@babel/core': 7.23.7
|
||||||
'@babel/helper-plugin-utils': 7.22.5
|
'@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):
|
/@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.7):
|
||||||
resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
|
resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
@ -6658,7 +6673,7 @@ packages:
|
||||||
magic-string: 0.27.0
|
magic-string: 0.27.0
|
||||||
react-docgen-typescript: 2.2.2(typescript@5.3.3)
|
react-docgen-typescript: 2.2.2(typescript@5.3.3)
|
||||||
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:
|
/@jridgewell/gen-mapping@0.3.3:
|
||||||
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
|
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
|
||||||
|
@ -9939,7 +9954,7 @@ packages:
|
||||||
magic-string: 0.30.5
|
magic-string: 0.30.5
|
||||||
rollup: 3.29.4
|
rollup: 3.29.4
|
||||||
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)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -10292,7 +10307,7 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-docgen: 6.0.4
|
react-docgen: 6.0.4
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
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:
|
transitivePeerDependencies:
|
||||||
- '@preact/preset-vite'
|
- '@preact/preset-vite'
|
||||||
- encoding
|
- encoding
|
||||||
|
@ -11694,7 +11709,7 @@ packages:
|
||||||
'@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.2)
|
'@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.2)
|
||||||
magic-string: 0.27.0
|
magic-string: 0.27.0
|
||||||
react-refresh: 0.14.0
|
react-refresh: 0.14.0
|
||||||
vite: 5.0.10(@types/node@18.17.19)
|
vite: 5.0.10(less@4.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -12366,6 +12381,19 @@ packages:
|
||||||
validate-html-nesting: 1.2.2
|
validate-html-nesting: 1.2.2
|
||||||
dev: true
|
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:
|
/babel-plugin-module-resolver@5.0.0:
|
||||||
resolution: {integrity: sha512-g0u+/ChLSJ5+PzYwLwP8Rp8Rcfowz58TJNCe+L/ui4rpzE/mg//JVX0EWBUYoxaextqnwuGHzfGp2hh0PPV25Q==}
|
resolution: {integrity: sha512-g0u+/ChLSJ5+PzYwLwP8Rp8Rcfowz58TJNCe+L/ui4rpzE/mg//JVX0EWBUYoxaextqnwuGHzfGp2hh0PPV25Q==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
|
@ -12589,6 +12617,15 @@ packages:
|
||||||
babel-plugin-jsx-dom-expressions: 0.37.11(@babel/core@7.23.7)
|
babel-plugin-jsx-dom-expressions: 0.37.11(@babel/core@7.23.7)
|
||||||
dev: true
|
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:
|
/bail@2.0.2:
|
||||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -16544,7 +16581,6 @@ packages:
|
||||||
|
|
||||||
/html-entities@2.3.3:
|
/html-entities@2.3.3:
|
||||||
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
|
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/html-minifier-terser@6.1.0:
|
/html-minifier-terser@6.1.0:
|
||||||
resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
|
resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
|
||||||
|
@ -24519,7 +24555,6 @@ packages:
|
||||||
|
|
||||||
/validate-html-nesting@1.2.2:
|
/validate-html-nesting@1.2.2:
|
||||||
resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==}
|
resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/validate-npm-package-license@3.0.4:
|
/validate-npm-package-license@3.0.4:
|
||||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||||
|
@ -24788,6 +24823,7 @@ packages:
|
||||||
rollup: 4.9.2
|
rollup: 4.9.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vite@5.0.10(less@4.2.0):
|
/vite@5.0.10(less@4.2.0):
|
||||||
resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==}
|
resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==}
|
||||||
|
@ -24823,7 +24859,6 @@ packages:
|
||||||
rollup: 4.9.2
|
rollup: 4.9.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
dev: true
|
|
||||||
|
|
||||||
/vite@5.0.10(sass@1.69.5):
|
/vite@5.0.10(sass@1.69.5):
|
||||||
resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==}
|
resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==}
|
||||||
|
|
Loading…
Reference in a new issue