//
// Copyright 2025 DXOS.org
//
import React, { memo, forwardRef, Suspense, useMemo, Fragment } from 'react';
import { useDefaultValue } from '@dxos/react-hooks';
import { byPosition } from '@dxos/util';
import { ErrorBoundary } from './ErrorBoundary';
import { useCapabilities } from './useCapabilities';
import { Capabilities, type SurfaceDefinition, type SurfaceProps } from '../common';
import { type PluginContext } from '../core';
const DEFAULT_PLACEHOLDER = ;
/**
* @internal
*/
export const useSurfaces = () => {
const surfaces = useCapabilities(Capabilities.ReactSurface);
return useMemo(() => surfaces.flat(), [surfaces]);
};
const findCandidates = (surfaces: SurfaceDefinition[], { role, data }: Pick) => {
return Object.values(surfaces)
.filter((definition) =>
Array.isArray(definition.role) ? definition.role.includes(role) : definition.role === role,
)
.filter(({ filter }) => (filter ? filter(data ?? {}) : true))
.toSorted(byPosition);
};
/**
* @returns `true` if there is a contributed surface which matches the specified role & data, `false` otherwise.
*/
export const isSurfaceAvailable = (context: PluginContext, { role, data }: Pick) => {
const surfaces = context.getCapabilities(Capabilities.ReactSurface);
const candidates = findCandidates(surfaces.flat(), { role, data });
return candidates.length > 0;
};
/**
* A surface is a named region of the screen that can be populated by plugins.
*/
export const Surface = memo(
forwardRef(
({ id: _id, role, data: _data, limit, fallback, placeholder = DEFAULT_PLACEHOLDER, ...rest }, forwardedRef) => {
// TODO(wittjosiah): This will make all surfaces depend on a single signal.
// This isn't ideal because it means that any change to the data will cause all surfaces to re-render.
// This effectively means that plugin modules which contribute surfaces need to all be activated at startup.
// This should be fine for now because it's how it worked prior to capabilities api anyways.
// In the future, it would be nice to be able to bucket the surface contributions by role.
const surfaces = useSurfaces();
const data = useDefaultValue(_data, () => ({}));
// NOTE: Memoizing the candidates makes the surface not re-render based on reactivity within data.
const definitions = findCandidates(surfaces, { role, data });
const candidates = limit ? definitions.slice(0, limit) : definitions;
const nodes = candidates.map(({ component: Component, id }) => (
));
const suspense = {nodes};
return fallback ? (
{suspense}
) : (
suspense
);
},
),
);
Surface.displayName = 'Surface';