@salsita-ai/sdk 0.50.0-develop.85e8f9df39 → 0.50.0-develop.8837f064f9
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/configurator/app-loader-content/DefaultAppLoaderContent.d.ts +2 -0
- package/dist/configurator/app-loader-content/DefaultAppLoaderContent.js +5 -0
- package/dist/configurator/app-loader-content/app-loader-content-name.d.ts +5 -0
- package/dist/configurator/app-loader-content/app-loader-content-name.js +1 -0
- package/dist/configurator/app-loader-content/index.d.ts +2 -0
- package/dist/configurator/app-loader-content/index.js +1 -0
- package/dist/configurator/common/configurator-interface.d.ts +46 -3
- package/dist/configurator/common/configurator-interface.js +1 -1
- package/dist/configurator/configuration-not-found-page/create-configuration-not-found-page.d.ts +9 -0
- package/dist/configurator/configuration-not-found-page/create-configuration-not-found-page.js +1 -0
- package/dist/configurator/configuration-not-found-page/index.d.ts +1 -0
- package/dist/configurator/configuration-not-found-page/index.js +1 -1
- package/dist/configurator/configurator-error-page/BuiltInConfiguratorErrorPage.d.ts +6 -0
- package/dist/configurator/configurator-error-page/BuiltInConfiguratorErrorPage.js +1 -0
- package/dist/configurator/configurator-error-page/ConfiguratorErrorPageElement.d.ts +12 -0
- package/dist/configurator/configurator-error-page/ConfiguratorErrorPageElement.js +1 -0
- package/dist/configurator/configurator-error-page/create-configurator-error-page.d.ts +9 -0
- package/dist/configurator/configurator-error-page/create-configurator-error-page.js +1 -0
- package/dist/configurator/configurator-error-page/index.d.ts +3 -0
- package/dist/configurator/configurator-error-page/index.js +1 -0
- package/dist/configurator/index.d.ts +2 -0
- package/dist/configurator/index.js +1 -1
- package/dist/configurator/pages/ConfiguratorErrorPage.d.ts +0 -1
- package/dist/configurator/pages/ConfiguratorErrorPage.js +1 -1
- package/dist/configurator/pages/LoadedConfiguratorDetailPage.js +1 -1
- package/dist/configurator/pages/PresetCreationErrorPage.d.ts +1 -1
- package/dist/configurator/pages/PresetCreationErrorPage.js +1 -1
- package/dist/configurator/pages/default-configurator-elements.d.ts +2 -1
- package/dist/configurator/pages/default-configurator-elements.js +1 -1
- package/dist/configurator/pages/error-boundaries/GeneralErrorBoundary.js +1 -1
- package/dist/configurator/ui/configurator-layout/slots/canvas/LayoutCanvas.js +1 -1
- package/dist/configurator/ui/configurator-layout/slots/canvas/SharedCanvas.js +1 -1
- package/dist/configurator/ui/legacy/layout/common/ReactiveInterfaceLayout.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/AppLoader.js +2 -6
- package/dist/docs/index.json +1 -1
- package/dist/docs/layouts/custom-layout.mdx +1 -1
- package/dist/docs/other-features/error-5xx.mdx +1 -1
- package/dist/docs/other-features/not-found-404.mdx +1 -1
- package/dist/docs/phoenix/configurator-interface.mdx +56 -24
- package/dist/docs/phoenix/layout-slots.mdx +4 -4
- package/dist/docs/phoenix/testing.mdx +2 -2
- package/dist/react-sdk/application-runtime/common-services/analytics/frontend-analytics.d.ts +4 -0
- package/dist/react-sdk/domain-context/common-hooks/index.d.ts +8 -0
- package/dist/react-sdk/domain-context/common-hooks/index.js +1 -0
- package/dist/react-sdk/domain-context/component-hooks/index.d.ts +1 -0
- package/dist/react-sdk/domain-context/component-hooks/index.js +1 -0
- package/dist/react-sdk/domain-context/component-hooks/use-component.js +1 -1
- package/dist/react-sdk/domain-context/entity-hooks/use-domain-entity-state.js +1 -1
- package/dist/react-sdk/domain-context/entity-hooks/use-entity-price.js +1 -1
- package/dist/react-sdk/domain-context/entity-hooks/use-root-entity-id.js +1 -1
- package/dist/react-sdk/domain-context/entity-hooks/use-root-entity-state.js +1 -1
- package/dist/react-sdk/domain-context/system-hooks/composition-hooks.js +1 -1
- package/dist/react-sdk/domain-context/system-hooks/dimension-hooks.js +1 -1
- package/dist/react-sdk/domain-context/system-hooks/hover-hooks.js +1 -1
- package/dist/react-sdk/domain-context/system-hooks/navigation-hooks.js +1 -1
- package/dist/react-sdk/domain-context/system-hooks/selection-hooks.js +1 -1
- package/dist/react-sdk/domain-context/system-hooks/tab-navigation-hooks.js +1 -1
- package/dist/react-sdk/domain-context/system-hooks/undo-redo-hooks.js +1 -1
- package/dist/react-sdk/elements/configurator-error-page-props.d.ts +20 -0
- package/dist/react-sdk/elements/configurator-error-page-props.js +1 -0
- package/dist/react-sdk/elements/index.d.ts +1 -0
- package/dist/react-sdk/loading-state/loading-state-manager.d.ts +5 -0
- package/dist/react-sdk/loading-state/loading-state-manager.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{jsx as t}from"react/jsx-runtime";import{keyframes as e,styled as r}from"@mui/material";let a=e`
|
|
2
|
+
0% { transform: perspective(200px) rotateX(0deg) rotateY(0deg) }
|
|
3
|
+
50% { transform: perspective(200px) rotateX(-180deg) rotateY(0deg) }
|
|
4
|
+
100% { transform: perspective(200px) rotateX(-180deg) rotateY(-180deg) }
|
|
5
|
+
`,o=r("span")(({theme:t})=>({animation:`${a} 1s linear infinite`,aspectRatio:1,background:t.palette.primary.main,height:36}));export const DefaultAppLoaderContent=()=>t(o,{});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const AppLoaderContentName="AppLoaderContent";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{AppLoaderContentName}from"./app-loader-content-name.js";export{DefaultAppLoaderContent}from"./DefaultAppLoaderContent.js";
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { type $DomainEntity, type ConfiguratorBuilder, type CreateCustomPhoenixEntityElement, type CreateCustomPhoenixLayoutElement, type CreateGetThemeHandler, type CreateGetWebComponentOptionsHandler, type CreatePhoenixHandler, type DomainEntity, type IsCreateRecipeUpdate, type IsReplaceRecipeUpdate, type TransformerBuilder, type TransformerFactory, type UpdateFactory, getIdSequence } from '#package/domain';
|
|
2
|
-
import { type CreateCollectFrontendDataHandler, type CreateFilterPublishedChunksHandler, type CreateNotAuthenticatedPage, type IsAngleDimensionUpdate, type IsAttributeSelectionUpdate, type IsAttributeSwitchUpdate, type IsDeleteEntityUpdate, type IsDistanceDimensionUpdate, type IsGenericDimensionUpdate, type IsSwatchSelectionUpdate, type IsTextValueUpdate, type UseAccessService, type UseActiveNavigationState, type UseActiveToolState, type UseActiveViewState, type UseAnalyticsService, type UseAngleDimension, type UseAttributeSelection, type UseAttributeSwitch, type UseAuthenticatedClient, type UseAuthorizedClient, type UseBackendData, type UseChildEntitySelection, type UseComponent, type UseComponents, type UseComposition, type UseConfigurationId, type UseConfiguratorMessages, type UseControlViolations, type UseCookRecipes, type UseCookReplaceRecipes, type UseCookedRecipes, type UseCookedReplaceRecipes, type UseCreateAuthorizedClient, type UseDeleteEntity, type UseDeletedEntity, type UseDeselect, type UseDistanceDimension, type UseDistanceDimensionSelection, type UseDomainApplicationContext, type UseDomainApplicationProps, type UseDomainContext, type UseDomainEntityState, type UseEntityPrice, type UseEntitySelector, type UseFinalizeAction, type UseFinalizeActions, type UseFinalizeViolations, type UseFirstComponent, type UseFirstRootComponent, type UseFrontendData, type UseGenericDimension, type UseGroupedRecipeOptions, type UseHoveredDomainEntityId, type UseHoveredEntity, type UseHoveredEntityId, type UseIntegrationService, type UseIntegrationState, type UseIsFullscreenActive, type UseLoadedData, type UseLoadingState, type UseMapViolationHints, type UseNavigate, type UsePopNavigate, type UsePresetEmbeddedState, type UsePublishedData, type UsePushNavigate, type UseRecipeEmbeddedState, type UseReplaceEntityRecipeOptions, type UseRootComponents, type UseRootEntityId, type UseRootEntityPrice, type UseRootEntitySelector, type UseRootEntityState, type UseSelectEntity, type UseSelectParent, type UseSelectedDomainEntityId, type UseSelectedEntity, type UseSelectedEntityId, type UseSession, type UseSessionRole, type UseSessionService, type UseSetFullscreen, type UseSetViewState, type UseSlice, type UseState, type UseStateValidity, type UseStepNavigation, type UseSwatchSelection, type UseSynchronizedSelector, type UseSynchronizedSelector$, type UseTabNavigation, type UseTextValue, type UseUndoRedo, type UseUpdateControl, type UseUpdatedControl, useLoadingStateValue } from '#package/react-sdk';
|
|
2
|
+
import { type CreateCollectFrontendDataHandler, type CreateFilterPublishedChunksHandler, type CreateNotAuthenticatedPage, type IsAngleDimensionUpdate, type IsAttributeSelectionUpdate, type IsAttributeSwitchUpdate, type IsDeleteEntityUpdate, type IsDistanceDimensionUpdate, type IsGenericDimensionUpdate, type IsSwatchSelectionUpdate, type IsTextValueUpdate, type UseAccessService, type UseActiveNavigationState, type UseActiveToolState, type UseActiveViewState, type UseActiveViewports, type UseAnalyticsService, type UseAngleDimension, type UseAttributeSelection, type UseAttributeSwitch, type UseAuthenticatedClient, type UseAuthorizedClient, type UseBackendData, type UseChildEntitySelection, type UseComponent, type UseComponents, type UseComposition, type UseConfigurationId, type UseConfiguratorMessages, type UseControlViolations, type UseCookRecipes, type UseCookReplaceRecipes, type UseCookedRecipes, type UseCookedReplaceRecipes, type UseCreateAuthorizedClient, type UseDeleteEntity, type UseDeletedEntity, type UseDeselect, type UseDistanceDimension, type UseDistanceDimensionSelection, type UseDomainApplicationContext, type UseDomainApplicationProps, type UseDomainContext, type UseDomainEntityState, type UseEntityPrice, type UseEntitySelector, type UseFinalizeAction, type UseFinalizeActions, type UseFinalizeViolations, type UseFirstComponent, type UseFirstRootComponent, type UseFrontendData, type UseGenericDimension, type UseGroupedRecipeOptions, type UseHoveredDomainEntityId, type UseHoveredEntity, type UseHoveredEntityId, type UseIntegrationService, type UseIntegrationState, type UseIsFullscreenActive, type UseLoadedData, type UseLoadingState, type UseMapViolationHints, type UseNavigate, type UsePopNavigate, type UsePresetEmbeddedState, type UsePublishedData, type UsePushNavigate, type UseRecipeEmbeddedState, type UseReplaceEntityRecipeOptions, type UseRootComponents, type UseRootEntityId, type UseRootEntityPrice, type UseRootEntitySelector, type UseRootEntityState, type UseSelectEntity, type UseSelectParent, type UseSelectedDomainEntityId, type UseSelectedEntity, type UseSelectedEntityId, type UseSession, type UseSessionRole, type UseSessionService, type UseSetFullscreen, type UseSetViewState, type UseSlice, type UseState, type UseStateValidity, type UseStepNavigation, type UseSwatchSelection, type UseSynchronizedSelector, type UseSynchronizedSelector$, type UseTabNavigation, type UseTextValue, type UseUndoRedo, type UseUpdateControl, type UseUpdatedControl, useLoadingStateValue, useLocalizable, useResolveLocalizable } from '#package/react-sdk';
|
|
3
3
|
import { type CreatePhoenixIconSet } from '#package/ui-sdk';
|
|
4
4
|
import { type UseConfiguratorExtensions } from '../application-runtime/index.js';
|
|
5
|
-
import { type
|
|
5
|
+
import { type CreateConfigurationNotFoundPage } from '../configuration-not-found-page/index.js';
|
|
6
|
+
import { type CreateOnApplicationStartProvider, type CreateOnBackendDataReadyProvider, type CreateOnConfiguratorReadyProvider, type CreateOnFrontendDataReadyProvider, type CreateOnGoToIndexHandler, type CreateOnPresetLoadedHandler, type CreateOnPublishedDataReadyProvider, type CreateOnRoutingReadyProvider, type CreateOnSessionReadyProvider, type UseConfiguratorRouting, type UsePresetSelection, usePreview, usePropertyPageStack, usePropertyPageState } from '../configurator-context/index.js';
|
|
7
|
+
import { type CreateConfiguratorErrorPage } from '../configurator-error-page/index.js';
|
|
8
|
+
import { useCloseAction } from '../integration/index.js';
|
|
6
9
|
import { type CreateGetTextureProcessingMapHandler } from '../scene/index.js';
|
|
7
10
|
import type { UseUpdateEntityObjectSelection } from '../scene/scene-entity/drag-and-drop/use-updated-selection.js';
|
|
8
|
-
import { type CreateAppLoader, type CreateCanvasLoader, type CreateLayoutAdminUi, type CreateLayoutCustomOwnerUi, type CreateLayoutCustomReadUi, type CreateLayoutDialog, type CreateLayoutFooter, type CreateLayoutToolbarDesktop, type CreateLayoutToolbarDesktopFullScreen, type CreateLayoutToolbarReadDesktop, type CreateLayoutToolbarReadDesktopFullScreen, type CreateLayoutUserUi, type CreateToolbarHelp } from '../ui/index.js';
|
|
11
|
+
import { type CreateAppLoader, type CreateCanvasLoader, type CreateLayoutAdminUi, type CreateLayoutCustomOwnerUi, type CreateLayoutCustomReadUi, type CreateLayoutDialog, type CreateLayoutFooter, type CreateLayoutToolbarDesktop, type CreateLayoutToolbarDesktopFullScreen, type CreateLayoutToolbarReadDesktop, type CreateLayoutToolbarReadDesktopFullScreen, type CreateLayoutUserUi, type CreateToolbarHelp, LayoutSlots, useSceneSafeArea } from '../ui/index.js';
|
|
9
12
|
import { useGenerateShareLink } from '../ui/phoenix-layout-elements/share/use-share.js';
|
|
10
13
|
import { type CreatePresetDeviceNotSupported, type CreatePresetPageAction, type CreatePresetUi } from '../ui/preset-layout/PresetUi.js';
|
|
11
14
|
import { type UseResetCamera } from './use-reset-camera.js';
|
|
@@ -180,6 +183,16 @@ export interface ConfiguratorInterface<ENTITIES extends DomainEntity<string>, AL
|
|
|
180
183
|
* Creates a custom page shown when session authentication fails.
|
|
181
184
|
*/
|
|
182
185
|
createNotAuthenticatedPage: CreateNotAuthenticatedPage;
|
|
186
|
+
/**
|
|
187
|
+
* Creates the page shown when a configuration cannot be loaded or opened, replacing the built-in
|
|
188
|
+
* `ConfigurationNotFoundPage`.
|
|
189
|
+
*/
|
|
190
|
+
createConfigurationNotFoundPage: CreateConfigurationNotFoundPage;
|
|
191
|
+
/**
|
|
192
|
+
* Creates the page the configurator shows in production when it cannot go on, replacing the built-in
|
|
193
|
+
* `ConfiguratorErrorPage`.
|
|
194
|
+
*/
|
|
195
|
+
createConfiguratorErrorPage: CreateConfiguratorErrorPage;
|
|
183
196
|
/**
|
|
184
197
|
* Factory for creating transformers for entities.
|
|
185
198
|
* @deprecated Use {@link #transformerBuilder} instead.
|
|
@@ -510,6 +523,36 @@ export interface ConfiguratorInterface<ENTITIES extends DomainEntity<string>, AL
|
|
|
510
523
|
* and the header's stepper do
|
|
511
524
|
*/
|
|
512
525
|
useStepNavigation: UseStepNavigation<NAVIGATION_STATES>;
|
|
526
|
+
/**
|
|
527
|
+
* Tell the camera which part of the canvas a project layout leaves free for the scene. Attach `containerRef` to the
|
|
528
|
+
* element that covers the whole canvas and `safeAreaRef` to the region the scene should stay visible in
|
|
529
|
+
*/
|
|
530
|
+
useSceneSafeArea: typeof useSceneSafeArea;
|
|
531
|
+
/**
|
|
532
|
+
* Get the responsive breakpoints the configurator's container satisfies, or `null` before it is measured
|
|
533
|
+
*/
|
|
534
|
+
useActiveViewports: UseActiveViewports;
|
|
535
|
+
/**
|
|
536
|
+
* Get the preview state: whether the layout shows a preview start screen, and functions that enter and leave it
|
|
537
|
+
*/
|
|
538
|
+
usePreview: typeof usePreview;
|
|
539
|
+
/**
|
|
540
|
+
* Get the host's close action, or `null` when the integration mode offers none
|
|
541
|
+
*/
|
|
542
|
+
useCloseAction: typeof useCloseAction;
|
|
543
|
+
/**
|
|
544
|
+
* Whether a viewer of a configuration shared for viewing may clone it to edit: the application config sets
|
|
545
|
+
* `shareCloneToEdit` and the session's role may create configurations
|
|
546
|
+
*/
|
|
547
|
+
useIsCloningAllowed: typeof LayoutSlots.useIsCloningAllowed;
|
|
548
|
+
/**
|
|
549
|
+
* Returns a function that turns a localizable text of the spec into a string in the current language
|
|
550
|
+
*/
|
|
551
|
+
useResolveLocalizable: typeof useResolveLocalizable;
|
|
552
|
+
/**
|
|
553
|
+
* Turn one localizable text of the spec into a string in the current language
|
|
554
|
+
*/
|
|
555
|
+
useLocalizable: typeof useLocalizable;
|
|
513
556
|
/**
|
|
514
557
|
* Returns composition tree of the domain
|
|
515
558
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createCustomPhoenixEntityElement as e,createCustomPhoenixLayoutElement as r,createGetThemeHandler as
|
|
1
|
+
import{createCustomPhoenixEntityElement as e,createCustomPhoenixLayoutElement as r,createGetThemeHandler as o,createGetWebComponentOptionsHandler as t,createPhoenixHandler as i,createTransformer as n,createTransformerBuilder as s,createUpdateFactory as a,getIdSequence as m,isCreateEntityUpdate as p,isReplaceEntityUpdate as u}from"#package/domain";import{createCollectFrontendDataHandler as d,createFilterPublishedChunksHandler as f,createNotAuthenticatedPage as c,isAngleDimensionUpdate as g,isAttributeSelectionUpdate as l,isAttributeSwitchUpdate as j,isDeleteEntityUpdate as x,isDistanceDimensionUpdate as y,isGenericDimensionUpdate as k,isSwatchSelectionUpdate as I,isTextValueUpdate as h,useAccessService as A,useActiveNavigationState as C,useActiveToolState as S,useActiveViewState as w,useActiveViewports as E,useAnalyticsService as q,useAngleDimension as B,useAttributeSelection as P,useAttributeSwitch as R,useAuthenticatedClient as U,useAuthorizedClient as b,useBackendData as v,useChildEntitySelection as z,useCommittedSlice as D,useCommittedState as F,useComponent as G,useComponents as H,useComposition as J,useConfigurationId as K,useConfiguratorMessages as L,useControlViolations as M,useCookRecipes as N,useCookReplaceRecipes as O,useCookedRecipes as Q,useCookedReplaceRecipes as T,useCreateAuthorizedClient as V,useDeleteEntity as W,useDeletedEntity as X,useDeselect as Y,useDistanceDimension as Z,useDistanceDimensionSelection as $,useDomain as _,useDomainApplication as ee,useDomainApplicationProps as er,useDomainEntityRenderedState as eo,useDomainEntityState as et,useEntityPrice as ei,useEntitySelector as en,useFinalizeAction as es,useFinalizeActions as ea,useFinalizeViolations as em,useFirstComponent as ep,useFirstRenderedComponent as eu,useFirstRootComponent as ed,useFirstRootRenderedComponent as ef,useFrontendData as ec,useGenericDimension as eg,useGroupedRecipeOptions as el,useHoveredDomainEntityId as ej,useHoveredEntity as ex,useHoveredEntityId as ey,useIntegrationService as ek,useIntegrationState as eI,useIsFullscreenActive as eh,useLoadedData as eA,useLoadingState as eC,useLoadingStateValue as eS,useLocalizable as ew,useMapViolationHints as eE,useNavigate as eq,usePopNavigate as eB,usePresetEmbeddedState as eP,usePublishedData as eR,usePushNavigate as eU,useRecipeEmbeddedState as eb,useRenderedComponent as ev,useRenderedComponents as ez,useRenderedEntitySelector as eD,useRenderedRootEntitySelector as eF,useRenderedSelector as eG,useRenderedSelector$ as eH,useRenderedSlice as eJ,useRenderedState as eK,useReplaceEntityRecipeOptions as eL,useResolveLocalizable as eM,useRootComponents as eN,useRootEntityId as eO,useRootEntityPrice as eQ,useRootEntityRenderedId as eT,useRootEntityRenderedState as eV,useRootEntitySelector as eW,useRootEntityState as eX,useRootRenderedComponents as eY,useSelectEntity as eZ,useSelectParent as e$,useSelectedDomainEntityId as e_,useSelectedEntity as e0,useSelectedEntityId as e1,useSession as e2,useSessionRole as e3,useSessionService as e4,useSetFullscreen as e5,useSetViewState as e6,useStateValidity as e7,useStepNavigation as e8,useStoreSelector as e9,useStoreSelector$ as re,useSwatchSelection as rr,useTabNavigation as ro,useTextValue as rt,useUndoRedo as ri,useUpdateControl as rn,useUpdatedControl as rs}from"#package/react-sdk";import{createPhoenixIconSet as ra}from"#package/ui-sdk";import{useConfigurator as rm}from"../application-runtime/index.js";import{createConfigurationNotFoundPage as rp}from"../configuration-not-found-page/index.js";import{createOnApplicationStartProvider as ru,createOnBackendDataReadyProvider as rd,createOnConfiguratorReadyProvider as rf,createOnFrontendDataReadyProvider as rc,createOnGoToIndexHandler as rg,createOnPresetLoadedHandler as rl,createOnPublishedDataReadyProvider as rj,createOnRoutingReadyProvider as rx,createOnSessionReadyProvider as ry,useConfiguratorRouting as rk,usePresetSelection as rI,usePreview as rh,usePropertyPageStack as rA,usePropertyPageState as rC}from"../configurator-context/index.js";import{createConfiguratorErrorPage as rS}from"../configurator-error-page/index.js";import{useCloseAction as rw}from"../integration/index.js";import{createGetTextureProcessingMapHandler as rE}from"../scene/index.js";import{useUpdateEntityObjectSelection as rq}from"../scene/scene-entity/drag-and-drop/use-updated-selection.js";import{LayoutSlots as rB,createAppLoader as rP,createCanvasLoader as rR,createLayoutAdminUi as rU,createLayoutCustomOwnerUi as rb,createLayoutCustomReadUi as rv,createLayoutDialog as rz,createLayoutFooter as rD,createLayoutToolbarDesktop as rF,createLayoutToolbarDesktopFullScreen as rG,createLayoutToolbarReadDesktop as rH,createLayoutToolbarReadDesktopFullScreen as rJ,createLayoutUserUi as rK,createToolbarHelp as rL,useSceneSafeArea as rM}from"../ui/index.js";import{useGenerateShareLink as rN}from"../ui/phoenix-layout-elements/share/use-share.js";import{createPresetDeviceNotSupported as rO,createPresetPageAction as rQ,createPresetUi as rT}from"../ui/preset-layout/PresetUi.js";import{useResetCamera as rV}from"./use-reset-camera.js";export const createConfiguratorInterface=rW=>({updates:a(),getIdSequence:m,createGetWebComponentOptionsHandler:t,createGetThemeHandler:o,createPhoenixHandler:i,createCustomPhoenixEntityElement:e,createCustomPhoenixLayoutElement:r,createPhoenixIconSet:ra,createGetTextureProcessingMapHandler:rE,createCollectFrontendDataHandler:d,createFilterPublishedChunksHandler:f,createOnGoToIndexHandler:rg,createOnApplicationStartProvider:ru,createOnSessionReadyProvider:ry,createOnPublishedDataReadyProvider:rj,createOnFrontendDataReadyProvider:rc,createOnBackendDataReadyProvider:rd,createOnRoutingReadyProvider:rx,createOnConfiguratorReadyProvider:rf,createOnPresetLoadedHandler:rl,createPresetUi:rT,createPresetPageAction:rQ,createPresetDeviceNotSupported:rO,createLayoutFooter:rD,createAppLoader:rP,createCanvasLoader:rR,createLayoutDialog:rz,createLayoutToolbarDesktop:rF,createLayoutToolbarDesktopFullScreen:rG,createLayoutToolbarReadDesktop:rH,createLayoutToolbarReadDesktopFullScreen:rJ,createLayoutCustomOwnerUi:rb,createLayoutCustomReadUi:rv,createLayoutAdminUi:rU,createLayoutUserUi:rK,createToolbarHelp:rL,createNotAuthenticatedPage:c,createConfigurationNotFoundPage:rp,createConfiguratorErrorPage:rS,createTransformer:n,transformerBuilder:s(),useAnalytics:q,useAuthenticatedClient:U,useAuthorizedClient:b,useCreateAuthorizedClient:V,useAccessService:A,useConfigurationId:K,useSession:e2,useSessionRole:e3,useSessionService:e4,useDomain:_,useDomainApplication:ee,useConfigurator:rm,useConfiguratorRouting:rk,usePresetSelection:rI,usePublishedData:eR,useFrontendData:ec,useBackendData:v,useDomainApplicationProps:er,useLoadedData:eA,useIntegrationService:ek,useIntegrationState:eI,usePresetEmbeddedState:eP,useRecipeEmbeddedState:eb,useCommittedSlice:D,useRenderedSlice:eJ,useCommittedState:F,useRenderedState:eK,useStoreSelector:e9,useRenderedSelector:eG,useStoreSelector$:re,useRenderedSelector$:eH,useEntitySelector:en,useRenderedEntitySelector:eD,useRootEntitySelector:eW,useRenderedRootEntitySelector:eF,useStateValidity:e7,useMapViolationHints:eE,useNavigate:eq,usePopNavigate:eB,usePushNavigate:eU,useControlViolations:M,useFinalizeViolations:em,useConfiguratorMessages:L,useFinalizeAction:es,useFinalizeActions:ea,useComponent:G,useRenderedComponent:ev,useComponents:H,useRenderedComponents:ez,useFirstComponent:ep,useFirstRenderedComponent:eu,useRootComponents:eN,useRootRenderedComponents:eY,useFirstRootComponent:ed,useFirstRootRenderedComponent:ef,useEntityState:et,useEntityRenderedState:eo,useRootEntityState:eX,useRootEntityRenderedState:eV,useRootEntityId:eO,useRootEntityRenderedId:eT,useEntityPrice:ei,useRootEntityPrice:eQ,useSelectedEntity:e0,useSelectedEntityId:e1,useSelectedDomainEntityId:e_,useSelectEntity:eZ,useDeselect:Y,useSelectParent:e$,useHoveredEntity:ex,useHoveredEntityId:ey,useHoveredDomainEntityId:ej,useActiveNavigationState:C,useActiveViewState:w,useSetViewState:e6,useActiveToolState:S,useIsFullscreenActive:eh,useSetFullscreen:e5,useResetCamera:rV,useUndoRedo:ri,useTabNavigation:ro,useStepNavigation:e8,useSceneSafeArea:rM,useActiveViewports:E,usePreview:rh,useCloseAction:rw,useIsCloningAllowed:rB.useIsCloningAllowed,useResolveLocalizable:eM,useLocalizable:ew,useComposition:J,useUpdateControl:rn,useUpdatedControl:rs,useCookRecipes:N,useCookedRecipes:Q,useCookReplaceRecipes:O,useCookedReplaceRecipes:T,useDeleteEntity:W,useDeletedEntity:X,useSwatchSelection:rr,useTextValue:rt,useAttributeSelection:P,useAttributeSwitch:R,useDistanceDimension:Z,useDistanceDimensionSelection:$,useGenericDimension:eg,useAngleDimension:B,useChildEntitySelection:z,useGroupedRecipeOptions:el,useReplaceEntityRecipeOptions:eL,useUpdateEntityObjectSelection:rq,useGenerateShareLink:rN,useLoadingState:eC,useLoadingStateValue:eS,usePropertyPageStack:rA,usePropertyPageState:rC,isCreateEntityUpdate:p,isReplaceEntityUpdate:u,isDeleteEntityUpdate:x,isAngleDimensionUpdate:g,isDistanceDimensionUpdate:y,isGenericDimensionUpdate:k,isAttributeSelectionUpdate:l,isAttributeSwitchUpdate:j,isTextValueUpdate:h,isSwatchSelectionUpdate:I});
|
package/dist/configurator/configuration-not-found-page/create-configuration-not-found-page.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PhoenixLayoutElementRegistration } from '#package/domain';
|
|
2
|
+
import type { ConfigurationNotFoundPageProps } from '#package/react-sdk';
|
|
3
|
+
/**
|
|
4
|
+
* Registers a project's "configuration not accessible" page as the `ConfigurationNotFoundPage`
|
|
5
|
+
* element, with its props typed. Registering a layout element under that name with
|
|
6
|
+
* `createCustomPhoenixLayoutElement` works too, but its component is typed as taking only `children`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const createConfigurationNotFoundPage: (Element: (props: ConfigurationNotFoundPageProps) => JSX.Element) => PhoenixLayoutElementRegistration;
|
|
9
|
+
export type CreateConfigurationNotFoundPage = (Element: (props: ConfigurationNotFoundPageProps) => JSX.Element) => PhoenixLayoutElementRegistration;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createCustomPhoenixLayoutElement as o}from"#package/domain";import{ConfigurationNotFoundPageName as t}from"./ConfigurationNotFoundPageElement.js";export const createConfigurationNotFoundPage=n=>o(t,n);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{ConfigurationNotFoundPageElement,ConfigurationNotFoundPageName}from"./ConfigurationNotFoundPageElement.js";
|
|
1
|
+
export{ConfigurationNotFoundPageElement,ConfigurationNotFoundPageName}from"./ConfigurationNotFoundPageElement.js";export{createConfigurationNotFoundPage}from"./create-configuration-not-found-page.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ConfiguratorErrorPageProps } from '#package/react-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* The configurator's built-in production error page, the default `ConfiguratorErrorPage`
|
|
4
|
+
* registration and the page shown when a project's error page throws.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BuiltInConfiguratorErrorPage: (props: ConfiguratorErrorPageProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jsx as e}from"react/jsx-runtime";import{match as t}from"ts-pattern";import{ErrorPage as o}from"#package/ui-sdk";export const BuiltInConfiguratorErrorPage=r=>{let{reason:n,onRetry:i}=r;return t(n).with("presetCreationFailed",()=>e(o,{title:"Whoops, something went wrong!",message:"We couldn't start configuring from this preset. Please reload the page and try again.",buttonLabel:"Reload page",onButtonClick:i})).with("uncaughtError",()=>e(o,{title:"Whoops, something went wrong!",message:"We're experiencing some technical difficulties right now. Please try again in a few minutes. Thank you for your patience!",buttonLabel:"Try Again",onButtonClick:i})).exhaustive()};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ConfiguratorErrorPageProps } from '#package/react-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* The name a project registers a layout element under to replace the configurator's production error
|
|
4
|
+
* page. Its default, `BuiltInConfiguratorErrorPage`, is registered in `defaultConfiguratorElements`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ConfiguratorErrorPageName = "ConfiguratorErrorPage";
|
|
7
|
+
/**
|
|
8
|
+
* Renders the `ConfiguratorErrorPage` element. The page is shown after something already failed, so
|
|
9
|
+
* a registered page that throws in turn falls back to the built-in page instead of taking the
|
|
10
|
+
* configurator down; a hot reload that replaces or removes that page renders the element again.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ConfiguratorErrorPageElement: (props: ConfiguratorErrorPageProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jsx as r}from"react/jsx-runtime";import{useCallback as e}from"react";import{ErrorBoundary as o}from"react-error-boundary";import{PhoenixLayoutElement as t,useLogger as a,usePhoenixElementSlots as n}from"#package/react-sdk";import{BuiltInConfiguratorErrorPage as i}from"./BuiltInConfiguratorErrorPage.js";export const ConfiguratorErrorPageName="ConfiguratorErrorPage";export const ConfiguratorErrorPageElement=g=>{let m=a(),{layoutElements:s}=n(),f=e(r=>{m.error("The ConfiguratorErrorPage element threw while rendering, so the built-in error page is shown instead. Fix the error in the registered ConfiguratorErrorPage element.",{},r)},[m]);return r(o,{fallback:r(i,{...g}),onError:f,resetKeys:[s[ConfiguratorErrorPageName]],children:r(t,{name:ConfiguratorErrorPageName,elementProps:{...g}})})};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PhoenixLayoutElementRegistration } from '#package/domain';
|
|
2
|
+
import type { ConfiguratorErrorPageProps } from '#package/react-sdk';
|
|
3
|
+
/**
|
|
4
|
+
* Registers a project's production error page as the `ConfiguratorErrorPage` element, with its props
|
|
5
|
+
* typed. Registering a layout element under that name with `createCustomPhoenixLayoutElement` works
|
|
6
|
+
* too, but its component is typed as taking only `children`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const createConfiguratorErrorPage: (Element: (props: ConfiguratorErrorPageProps) => JSX.Element) => PhoenixLayoutElementRegistration;
|
|
9
|
+
export type CreateConfiguratorErrorPage = (Element: (props: ConfiguratorErrorPageProps) => JSX.Element) => PhoenixLayoutElementRegistration;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createCustomPhoenixLayoutElement as r}from"#package/domain";import{ConfiguratorErrorPageName as o}from"./ConfiguratorErrorPageElement.js";export const createConfiguratorErrorPage=a=>r(o,a);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { BuiltInConfiguratorErrorPage } from './BuiltInConfiguratorErrorPage.js';
|
|
2
|
+
export { ConfiguratorErrorPageElement, ConfiguratorErrorPageName } from './ConfiguratorErrorPageElement.js';
|
|
3
|
+
export { createConfiguratorErrorPage, type CreateConfiguratorErrorPage } from './create-configurator-error-page.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{BuiltInConfiguratorErrorPage}from"./BuiltInConfiguratorErrorPage.js";export{ConfiguratorErrorPageElement,ConfiguratorErrorPageName}from"./ConfiguratorErrorPageElement.js";export{createConfiguratorErrorPage}from"./create-configurator-error-page.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { ConfiguratorApp } from './ConfiguratorApp.js';
|
|
2
|
+
export { createConfigurationNotFoundPage, type CreateConfigurationNotFoundPage, } from './configuration-not-found-page/index.js';
|
|
3
|
+
export { createConfiguratorErrorPage, type CreateConfiguratorErrorPage } from './configurator-error-page/index.js';
|
|
2
4
|
export * from './application-runtime/index.js';
|
|
3
5
|
export * from './common/index.js';
|
|
4
6
|
export * from './configurator-context/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{ConfiguratorApp}from"./ConfiguratorApp.js";export*from"./application-runtime/index.js";export*from"./common/index.js";export*from"./configurator-context/index.js";export*from"./pages/index.js";export*from"./scene/index.js";export*from"./ui/index.js";export*from"./integration/index.js";
|
|
1
|
+
export{ConfiguratorApp}from"./ConfiguratorApp.js";export{createConfigurationNotFoundPage}from"./configuration-not-found-page/index.js";export{createConfiguratorErrorPage}from"./configurator-error-page/index.js";export*from"./application-runtime/index.js";export*from"./common/index.js";export*from"./configurator-context/index.js";export*from"./pages/index.js";export*from"./scene/index.js";export*from"./ui/index.js";export*from"./integration/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as e}from"react/jsx-runtime";import{Trans as o}from"@lingui/react";import{ErrorPage as r}from"#package/ui-sdk";
|
|
1
|
+
import{jsx as e}from"react/jsx-runtime";import{Trans as o}from"@lingui/react";import{ErrorPage as r}from"#package/ui-sdk";export const ConfiguratorRootErrorPage=()=>e(r,{title:e(o,{id:"sdk.messages.error.configuratorRoot.title",message:"Whoops, something went wrong!"}),message:e(o,{id:"sdk.messages.error.configuratorRoot.message",message:"We’re experiencing some technical difficulties right now. Please try again in a few minutes. Thank you for your patience!"}),buttonLabel:e(o,{id:"sdk.messages.error.configuratorRoot.backToHome.label",message:"Back to Home"})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as o,jsxs as r}from"react/jsx-runtime";import
|
|
1
|
+
import{jsx as o,jsxs as r}from"react/jsx-runtime";import i from"mui-modal-provider";import{memo as e,useCallback as n,useEffect as t,useState as a}from"react";import{InvalidAccessKeyError as d}from"#package/client";import{BuiltInLoadingPhase as l,CommonAnalyticsFactory as s,DomainLifecycleBoundary as c,FinalizeActionsHost as u,FinalizeActionsProvider as f,LoadedDataProvider as m,PhoenixLayoutElement as g,useAccessService as p,useAnalyticsService as v,useAuthorizedClient as I,useConfiguratorDomainFactory as h,useConfiguratorLoadStatus as j,useConfiguratorRawLoadedData as C,useLoadedDataActions as k,useLoadingError as w,useLoadingPhase as y,useLogger as b,useRoutingService as x,useSpecAssistant as M}from"#package/react-sdk";import{useConfigurator as E}from"../application-runtime/index.js";import{IntegrationSubscription as L,OnConfiguratorReadyProvider as D,ViewportStateSync as F}from"../configurator-context/index.js";import{ConfiguratorNotFoundPage as T}from"./ConfiguratorNotFoundPage.js";import{ConfiguratorLayoutName as P}from"./configurator-layout-name.js";import{DevContextFallback as R}from"./error-boundaries/DevContextFallback.js";import{DevLoadingFallback as V}from"./error-boundaries/DevLoadingFallback.js";import{LoadingErrorBoundary as A}from"./error-boundaries/LoadingErrorBoundary.js";import{useLoad as S}from"./use-load.js";let q=({onMount:o})=>(t(()=>{o()},[o]),null),B=e(({onLayoutMount:e})=>((()=>{let{assistantSystem:o}=E(),r=null!=M();t(()=>{if(r)return o.activate()},[o,r])})(),o(f,{children:o(i,{children:r(D,{children:[o(u,{}),o(g,{name:P}),o(q,{onMount:e})]})})})));export const LoadedConfiguratorDetailPage=()=>{let[i,e]=a(!1),t=n(()=>{e(!0)},[]),{configurationId:u,accessInfo:f}=p(),g=I(),M=x(),E=b(),D=v(),{startConfiguratorLoad:P,completeConfiguratorLoad:q,failConfiguratorLoad:G,resetConfiguratorLoad:N}=k(),{isLoading:z,isError:H}=j(),J=C(),K=h(),O=n(()=>f.viewMode&&void 0!==f.revisionId?g.getConfigurationRevision(u,f.revisionId):g.getConfiguration(u),[f.revisionId,f.viewMode,g,u]),Q=n(()=>{P()},[P]),U=n(o=>{q({initialRevisionId:o.revisionId,initialEntities:o.data,modelVersion:o.modelVersion,rootEntityType:o.rootEntityType,schemaIdentity:K}),D.push(s.ConfigurationLoaded(o.configurationId,o.revisionId,f.viewMode?"read":"owner")),E.appendToGlobalContext({configurationId:o.configurationId,revisionId:o.revisionId,mode:f.viewMode}),E.info("Configuration loaded.")},[f.viewMode,D,q,K,E]),W=n(o=>{if(G(o),o instanceof d)return void M.rejectAccessToken(u,f.raw);let r=o instanceof Error?o:Error("Configuration loading failed with an unknown error.",{cause:o});E.error("Configuration loading failed because the API request did not succeed. Verify the configuration ID and access token.",{configurationId:u},r)},[f.raw,u,G,E,M]),X=n(()=>{N()},[N]);return(S({key:[g,u,f.revisionId,f.viewMode],load:O,onStart:Q,onSuccess:U,onError:W,onCleanup:X}),y({id:l.DataFetch,label:"Loading configuration…"},z||!H&&!i),w(H),H)?o(T,{}):null===J?null:o(m,{value:J,children:o(A,{fallbackRender:r=>o(V,{...r}),children:o(c,{type:"configurator",children:o(A,{FallbackComponent:R,children:r(L,{children:[o(F,{}),o(B,{onLayoutMount:t})]})})})})})};
|
|
@@ -4,7 +4,7 @@ interface PresetCreationErrorPageProps {
|
|
|
4
4
|
/**
|
|
5
5
|
* Page shown when creating a configuration from a preset fails. Renders the
|
|
6
6
|
* detailed `DevErrorPage` in development (with extra guidance for known errors)
|
|
7
|
-
* and the
|
|
7
|
+
* and the `ConfiguratorErrorPage` element in production. The action reloads the page rather
|
|
8
8
|
* than retrying, since the common causes are not resolved by a retry.
|
|
9
9
|
*/
|
|
10
10
|
export declare const PresetCreationErrorPage: (props: PresetCreationErrorPageProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as e,jsxs as
|
|
1
|
+
import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Box as t,Typography as o}from"@mui/material";import{useCallback as a}from"react";import{isConfigurationSchemaMismatchError as i}from"#package/client";import{isStateMigrationError as n}from"#package/domain";import{useIsDev as d}from"#package/react-sdk";import{DevErrorPage as s}from"#package/ui-sdk";import{ConfiguratorErrorPageElement as l}from"../configurator-error-page/index.js";let m=a=>{let{heading:i,details:n}=a;return r(t,{component:"div",sx:{p:2,backgroundColor:"#ffebee",borderRadius:1,borderLeft:"4px solid #f44336"},children:[e(o,{variant:"subtitle2",sx:{fontWeight:600,mb:1,color:"error.main"},children:i}),e(o,{variant:"body2",sx:{mb:1},children:"Update the preset data to match the current model, or re-register/redeploy the schema. Details:"}),e(t,{component:"pre",sx:{m:0,p:1.5,backgroundColor:"#fff",borderRadius:1,border:"1px solid #e0e0e0",fontFamily:"monospace",fontSize:"0.75rem",whiteSpace:"pre-wrap",overflowX:"auto"},children:n})]})};export const PresetCreationErrorPage=r=>{let{error:t}=r,o=d(),c=a(()=>{window.location.reload()},[]);if(!o)return e(l,{reason:"presetCreationFailed",onRetry:c});let p=i(t)?{title:"Preset does not match the registered schema (Dev Mode)",heading:"⚠️ Preset data rejected by the configuration schema on the server",details:t.details}:n(t)?{title:"Preset could not be migrated to the current model (Dev Mode)",heading:"⚠️ Preset data is invalid for the current domain model",details:t.message}:null;return e(s,{title:p?.title??"Failed to create configuration (Dev Mode)",subtitle:"Creating a configuration from the preset failed. This detailed view is only visible in development mode.",errorName:t.name,errorMessage:t.message,errorStack:p?null:t.stack,error:t,onReset:c,resetLabel:"Reload page",children:p&&e(m,{heading:p.heading,details:p.details})})};
|
|
@@ -5,7 +5,8 @@ import { type ElementRegistration } from '#package/domain';
|
|
|
5
5
|
* loaded configuration, `ConfigurationNotFoundPage` the page shown when a configuration cannot be
|
|
6
6
|
* loaded or opened, `NotAuthenticatedPage` the page shown when session authentication fails or an
|
|
7
7
|
* anonymous visitor who may not create a configuration opens the start page, `PresetUi` the preset
|
|
8
|
-
* screen,
|
|
8
|
+
* screen, `ConfiguratorErrorPage` the production error page, `AppLoaderContent` what the app loader
|
|
9
|
+
* overlay shows, and the others are the built-in controls, toolbar buttons, menu actions and
|
|
9
10
|
* sidebar-footer pieces under the spec kinds that YAML names.
|
|
10
11
|
*/
|
|
11
12
|
export declare const defaultConfiguratorElements: readonly ElementRegistration[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createCustomPhoenixLayoutElement as o}from"#package/domain";import{ConfigurationNotFoundPage as
|
|
1
|
+
import{createCustomPhoenixLayoutElement as o}from"#package/domain";import{ConfigurationNotFoundPage as r,createNotAuthenticatedPage as t}from"#package/react-sdk";import{AppLoaderContentName as e,DefaultAppLoaderContent as i}from"../app-loader-content/index.js";import{createConfigurationNotFoundPage as m}from"../configuration-not-found-page/index.js";import{BuiltInConfiguratorErrorPage as a,createConfiguratorErrorPage as f}from"../configurator-error-page/index.js";import{createPresetUi as n}from"../ui/index.js";import{DefaultConfiguratorLayout as p}from"./DefaultConfiguratorLayout.js";import{DefaultNotAuthenticatedPage as u}from"./DefaultNotAuthenticatedPage.js";import{DefaultPresetUi as s}from"./DefaultPresetUi.js";import{ConfiguratorLayoutName as d}from"./configurator-layout-name.js";import{domainControlElements as g,recipePanelElements as c,sidebarFooterElements as j,toolbarElements as l}from"./default-elements/index.js";export const defaultConfiguratorElements=[o(d,p),m(r),t(u),n(s),f(a),o(e,i),...g,...c,...l,...j];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as e,jsxs as
|
|
1
|
+
import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Box as t,Stack as n,Typography as i}from"@mui/material";import{useApplicationRuntime as o,useIsDev as a}from"#package/react-sdk";import{DevErrorPage as l}from"#package/ui-sdk";import{ConfiguratorErrorPageElement as c}from"../../configurator-error-page/index.js";import{useResetOnEditorHotReload as d}from"./use-reset-on-editor-hot-reload.js";let s=()=>{let a=o();try{let o=a.selectors.domainContext.configurator;if(!o.getInitializationState().isCurrent)return null;let{storeSystem:l}=o.getDomainSystems(),c=l.stateSlices.committed.value,d=l.stateSlices.potentiallyInvalid.value,s=d.validity.valid||"uncaught"===d.validity.severity?[]:d.validity.entityViolations,m=s.length>0,p="reset"!==c.kind?c.updates:[];if(!m&&0===p.length)return null;return r(t,{component:"div",sx:{p:2,backgroundColor:m?"#ffebee":"#e3f2fd",borderRadius:1,borderLeft:m?"4px solid #f44336":"4px solid #2196f3"},children:[e(i,{variant:"subtitle2",sx:{fontWeight:600,mb:1,color:m?"error.main":"primary.main"},children:m?"⚠️ Error likely caused by invariant violations":"\uD83D\uDD04 Last state changes before error"}),m&&r(t,{component:"div",sx:{mb:2},children:[e(i,{variant:"body2",sx:{mb:1},children:"The following invariant violations were detected:"}),e(n,{component:"div",spacing:.5,children:s.map((e,t)=>r(i,{variant:"caption",sx:{fontFamily:"monospace",fontSize:"0.75rem",display:"block",pl:2},children:["• Entity ",String(e.entityId),": ",JSON.stringify(e.violation)]},t))})]}),p.length>0&&r(t,{component:"div",children:[r(i,{variant:"body2",sx:{mb:1},children:[p.length," update",1!==p.length?"s":""," applied just before the error:"]}),r(n,{component:"div",spacing:.5,children:[p.slice(0,5).map((e,t)=>{let n="";return"createDomainEntity"===e.kind?n=` (${e.entity.type})`:"deleteEntity"===e.kind||"modifyDomainEntity"===e.kind||"replaceDomainEntity"===e.kind?n=` (${e.entityId})`:("createEphemeralRecord"===e.kind||"modifyEphemeralRecord"===e.kind||"deleteEphemeralRecord"===e.kind)&&(n=` (${e.componentInstance.entityId})`),r(i,{variant:"caption",sx:{fontFamily:"monospace",fontSize:"0.75rem",display:"block",pl:2},children:["• ",e.kind,n]},t)}),p.length>5&&r(i,{variant:"caption",sx:{fontFamily:"monospace",fontSize:"0.75rem",display:"block",pl:2,fontStyle:"italic"},children:["... and ",p.length-5," more"]})]})]})]})}catch{return null}};export const DevGeneralErrorFallback=r=>{let{error:t,resetErrorBoundary:n}=r;return d(n),e(l,{title:"Application Error (Dev Mode)",subtitle:"An uncaught error occurred in the application. This detailed view is only visible in development mode.",errorName:t instanceof Error?t.name:"Error",errorMessage:t instanceof Error?t.message:String(t),errorStack:t instanceof Error?t.stack:null,error:t,onReset:n,resetLabel:"Try Again",children:e(s,{})})};export const ProdGeneralErrorFallback=r=>{let{resetErrorBoundary:t}=r;return e(c,{reason:"uncaughtError",onRetry:t})};export const GeneralErrorFallback=r=>a()?e(DevGeneralErrorFallback,{...r}):e(ProdGeneralErrorFallback,{...r});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as e,jsxs as t,Fragment as r}from"react/jsx-runtime";import{Box as a}from"@mui/material";import{useDetectGPU as i}from"@react-three/drei";import{Canvas as o,useThree as n}from"@react-three/fiber";import{memo as s,useCallback as l,useEffect as p,useMemo as c,useState as f}from"react";import{BuiltInLoadingPhase as m,
|
|
1
|
+
import{jsx as e,jsxs as t,Fragment as r}from"react/jsx-runtime";import{Box as a}from"@mui/material";import{useDetectGPU as i}from"@react-three/drei";import{Canvas as o,useThree as n}from"@react-three/fiber";import{memo as s,useCallback as l,useEffect as p,useMemo as c,useState as f}from"react";import{BuiltInLoadingPhase as m,useExpectPhase as d,useLoadingPhase as h,useLoadingStateManager as u,useStoreSelector as g}from"#package/react-sdk";import{useResponsive as x}from"#package/ui-sdk";import{useConfigurator as v}from"../../../../application-runtime/index.js";import{Scene as C,SceneEnvironment as P}from"../../../../scene/index.js";import{CanvasLoader as w}from"../../../phoenix-layout-elements/index.js";import{GPUIssuePage as j}from"./GPUIssuePage.js";let D=e=>{let{onCanNotLoad:t}=e,r=n(e=>e.gl);return p(()=>{let e=r.getContext(),a=e.getExtension("WEBGL_debug_renderer_info");a?e.getParameter(a.UNMASKED_RENDERER_WEBGL).toLocaleLowerCase().includes("swiftshader")&&t?.("no-acceleration"):t?.("no-acceleration")},[r,t]),null};export const Canvas=s(()=>{let[n,s]=f(null),[E,B]=f(!0),{canvasSystem:L}=v(),y=g(e=>L.getCanvasConfig(e.state),[L]),k=i(),{canvasPadding:A}=x(),M=null!==u();d(m.SceneAssets),h({id:m.SceneAssets,label:"Loading 3D scene…"},E);let $=l(()=>{B(!1),L.reportFirstLoadFinished()},[L]),_=l(e=>{L.onCanvasClick$.next(e)},[L]),b=l(e=>{L.onCanvasPointerMove$.next(e)},[L]),I=c(()=>{let e={};return y?.precision&&(e.precision=y.precision),y?.alpha&&(e.alpha=y.alpha),y?.premultipliedAlpha&&(e.premultipliedAlpha=y.premultipliedAlpha),y?.antialias&&(e.antialias=y.antialias),y?.stencil&&(e.stencil=y.stencil),y?.preserveDrawingBuffer&&(e.preserveDrawingBuffer=y.preserveDrawingBuffer),y?.powerPreference&&(e.powerPreference=y.powerPreference),y?.depth&&(e.depth=y.depth),y?.logarithmicDepthBuffer&&(e.logarithmicDepthBuffer=y.logarithmicDepthBuffer),y?.failIfMajorPerformanceCaveat&&(e.failIfMajorPerformanceCaveat=y.failIfMajorPerformanceCaveat),e},[y]);return(p(()=>{"WEBGL_UNSUPPORTED"===k.type&&s("no-gpu")},[k,s]),n)?e(j,{issue:n}):t(r,{children:[e(a,{component:"div",sx:{transition:"opacity 0.3s ease-in",opacity:+!E,width:"100%",height:"100%"},children:t(o,{gl:I,onClick:_,onPointerMove:b,frameloop:y?.frameloop,shadows:y?.shadows,children:[e(C,{onFirstLoad:$}),e(D,{onCanNotLoad:s}),e(P,{})]})}),!M&&e(a,{component:"div",sx:{position:"absolute",top:A.top,left:A.left,width:`calc(100% - ${A.left}px - ${A.right}px)`,height:`calc(100% - ${A.top}px - ${A.bottom}px)`,display:"flex",justifyContent:"center",alignItems:"center",transition:"opacity 0.3s ease-in",opacity:+!!E,pointerEvents:"none"},children:e(w,{})})]})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as t}from"react/jsx-runtime";import{styled as e}from"@mui/material";import{Suspense as o,memo as r,useEffect as i,useRef as n,useState as a}from"react";import{useLoadingStateManager as
|
|
1
|
+
import{jsx as t}from"react/jsx-runtime";import{styled as e}from"@mui/material";import{Suspense as o,memo as r,useEffect as i,useRef as n,useState as a}from"react";import{BuiltInLoadingPhase as s,useExpectPhase as l,useLoadingStateManager as m}from"#package/react-sdk";import{usePreview as c}from"../../../../configurator-context/index.js";import{CanvasLoader as p}from"../../../phoenix-layout-elements/index.js";import{Canvas as u}from"./LayoutCanvas.js";let d=e("div",{shouldForwardProp:t=>"masked"!==t})(({masked:t,theme:{palette:e}})=>({backgroundColor:e.background.scene,inset:0,position:"absolute",isolation:"isolate",maxBlockSize:"var(--phx-app-height)","&::after":{backgroundColor:"inherit",content:'""',display:"block",inset:"inherit",opacity:+!!t,pointerEvents:"none",position:"absolute",transitionDuration:t?"0ms":"100ms",transitionProperty:"opacity",transitionTimingFunction:"linear"}})),h=e("div")({display:"grid",inset:0,placeItems:"center",pointerEvents:"none",position:"absolute"}),f=()=>null!==m()?null:t(h,{children:t(p,{})});export const SharedCanvas=r(()=>{l(s.SceneAssets);let{isExpanded:e}=c(),[r,m]=a(!1),p=n(e);return i(()=>{if(e===p.current)return;p.current=e,m(!0);let t=setTimeout(()=>m(!1),600);return()=>clearTimeout(t)},[e]),t(d,{masked:r,children:t(o,{fallback:t(f,{}),children:t(u,{})})})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as e,jsxs as t,Fragment as o}from"react/jsx-runtime";import{Box as i,styled as r}from"@mui/material";import{AnimatePresence as a}from"motion/react";import{Suspense as n,memo as
|
|
1
|
+
import{jsx as e,jsxs as t,Fragment as o}from"react/jsx-runtime";import{Box as i,styled as r}from"@mui/material";import{AnimatePresence as a}from"motion/react";import{Suspense as n,memo as s,useEffect as l,useState as m}from"react";import{BuiltInLoadingPhase as c,useExpectPhase as d,useIsFullscreenActive as p,useLoadingStateManager as u,useResolveLocalizable as f}from"#package/react-sdk";import{useResponsive as b}from"#package/ui-sdk";import{usePreview as g}from"../../../../configurator-context/index.js";import{CanvasLoader as v}from"../../../phoenix-layout-elements/index.js";import{InterfaceLayout as x}from"../old/InterfaceLayout.js";import{PreviewLayout as h}from"../old/PreviewLayout.js";let k=({overlay:t})=>e("div",{style:{position:"absolute"},children:t}),y=r("div")({display:"grid",inset:0,placeItems:"center",pointerEvents:"none",position:"absolute"}),j=()=>null!==u()?null:e(y,{children:e(v,{})}),C=({canvas:t})=>{d(c.SceneAssets);let[o,r]=m(!0),{isPreview:a}=g();return l(()=>{r(!1);let e=setTimeout(()=>{r(!0)},400);return()=>{clearTimeout(e)}},[a]),e(i,{component:"div",sx:{backgroundColor:"background.scene",inset:0,isolation:"isolate",maxBlockSize:"var(--phx-app-height)",position:"absolute"},children:e(n,{fallback:e(j,{}),children:o&&t})})},S={id:"sdk.preview.customize.title",message:"Customize product"},L={id:"sdk.preview.start.button.label",message:"Start configuring"};export const ReactiveInterfaceLayout=s(i=>{let{canvas:r,sidebar:n,topNavigation:s,branding:l,toolbar:m,fullScreenToolbar:c,viewControls:d,toolControls:u,overlay:v,mobileControls:y,mobileTopNavigation:j,mobileHeaderActionButton:w,mobileHeaderCenter:z,footer:B,sideEffects:I,LayoutContainer:P}=i,{setCanvasPadding:T}=b(),A=p(),{isPreview:E,isHidden:F}=g(),H=f();return t(o,{children:[e(C,{canvas:r}),e(k,{overlay:v}),t(a,{children:[!F&&E&&e(h,{title:H(S),startButtonLabel:H(L),updateScenePadding:T}),!F&&!E&&e(x,{sidebar:n,topNavigation:s,branding:l,toolbar:m,toolbarSecondary:c,mobileControls:y,mobileTopNavigation:j,mobileHeaderActionButton:w,mobileHeaderCenter:z,footer:B,sideEffects:I,isFullScreen:A,updateScenePadding:T,LayoutContainer:P,viewControls:d,toolControls:u})]})]})});
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import{jsx as e,jsxs as t,Fragment as r}from"react/jsx-runtime";import{keyframes as
|
|
2
|
-
0% { transform: perspective(200px) rotateX(0deg) rotateY(0deg) }
|
|
3
|
-
50% { transform: perspective(200px) rotateX(-180deg) rotateY(0deg) }
|
|
4
|
-
100% { transform: perspective(200px) rotateX(-180deg) rotateY(-180deg) }
|
|
5
|
-
`,g=o("span")(({theme:e})=>({animation:`${d} 1s linear infinite`,aspectRatio:1,background:e.palette.primary.main,height:36})),x=a`
|
|
1
|
+
import{jsx as e,jsxs as t,Fragment as r}from"react/jsx-runtime";import{keyframes as o,styled as a}from"@mui/material";import{useRef as s}from"react";import{createCustomPhoenixLayoutElement as i}from"#package/domain";import{BuiltInLoadingPhase as n,PhoenixLayoutElement as p,useLoadingStateValue as m,useResponsiveState as c}from"#package/react-sdk";import{AppLoaderContentName as l}from"../../app-loader-content/index.js";export const createAppLoader=t=>[i(l,()=>{let{phases:r}=m();return e(t,{phases:r})})];let d=o`
|
|
6
2
|
from { opacity: 0 }
|
|
7
3
|
to { opacity: 1 }
|
|
8
|
-
`,
|
|
4
|
+
`,x=a("div")({animation:`${d} 1000ms linear 300ms forwards`,opacity:0,display:"grid",inset:0,maxBlockSize:"var(--phx-app-height)",placeItems:"center",pointerEvents:"none",position:"absolute",transition:"inset 100ms ease-in-out",zIndex:9999});export const AppLoader=({mode:o="universal",children:a})=>{let{isLoading:i,hasError:d,phases:f,expectedPhaseIds:u}=m(),{canvasPadding:h}=c(),g=s(!1);if(f.some(e=>e.id===n.SceneAssets)&&(g.current=!0),"legacy"===o)return a;let y=u.has(n.SceneAssets)&&!g.current;return t(r,{children:[a,!d&&(i||y)&&e(x,{style:{inset:`${h.top}px ${h.right}px ${h.bottom}px ${h.left}px`},children:e(p,{name:l,elementProps:{phases:f}})})]})};
|
package/dist/docs/index.json
CHANGED
|
@@ -164,7 +164,7 @@ The components it uses live in `src/_shared/`, which Phoenix does not import by
|
|
|
164
164
|
## What the layout has to do
|
|
165
165
|
|
|
166
166
|
- **Position the root and cover the page.** `LayoutSlots.SharedCanvas` is absolutely positioned and fills its nearest positioned ancestor. The root here is `position: absolute; inset: 0`.
|
|
167
|
-
- **Mount the shared canvas.**
|
|
167
|
+
- **Mount the shared canvas.** It renders the scene, and the app loader stays on screen until the scene has loaded. A layout without a canvas is not held by the loader: it leaves once the configuration is up.
|
|
168
168
|
- **Put the UI above the canvas.** The canvas is opaque. Content that is positioned and comes after it in the markup is painted above it; unpositioned content is painted under it and cannot be seen.
|
|
169
169
|
- **Tell the camera where the free area is.** `useSceneSafeArea()` returns two refs. `containerRef` goes on the element that covers the whole canvas, `safeAreaRef` on the region the product should stay visible in, here everything right of the panel, or above the sheet on a phone. The camera then frames the product in that region. The hook measures again whenever either of the two elements resizes; for a change no resize reports, such as a `transform` on an ancestor settling at the end of an animation, it also returns `update` to call. Without the two refs the product is centred in the whole canvas and sits against the panel.
|
|
170
170
|
- **Let the pointer through.** The safe-area element lies over the canvas, so it sets `pointerEvents: 'none'`; otherwise the scene cannot be orbited. The fullscreen button inside it switches pointer events back on.
|
|
@@ -12,7 +12,7 @@ Error page that is displayed when needed.
|
|
|
12
12
|
|
|
13
13
|
## Code
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
In production the configurator renders the layout element `ConfiguratorErrorPage` when an error reaches its error boundary or a configuration cannot be created from the only preset. Its default is a built-in page made with `ErrorPage` from `@salsita-ai/sdk/ui`; this example renders `ErrorPage` directly to show it. A project replaces the page with `phx.frontend.createConfiguratorErrorPage`, see [Configurator Interface → Configurator Error Page](?path=/docs/phoenix-configurator-interface--docs#configurator-error-page), which also lists the cases that still show another page.
|
|
16
16
|
|
|
17
17
|
## Example
|
|
18
18
|
|
|
@@ -12,7 +12,7 @@ Error page that is displayed when the configuration is not accesible.
|
|
|
12
12
|
|
|
13
13
|
## Code
|
|
14
14
|
|
|
15
|
-
The configurator renders the layout element `ConfigurationNotFoundPage`, whose default is the built-in page, `ConfigurationNotFoundPage` from `@salsita-ai/sdk/react`. This example renders that component directly to show it. A project replaces the page
|
|
15
|
+
The configurator renders the layout element `ConfigurationNotFoundPage`, whose default is the built-in page, `ConfigurationNotFoundPage` from `@salsita-ai/sdk/react`. This example renders that component directly to show it. A project replaces the page with `phx.frontend.createConfigurationNotFoundPage`, see [Configurator Interface → Configuration Not Found Page](?path=/docs/phoenix-configurator-interface--docs#configuration-not-found-page), which also lists when the page is shown.
|
|
16
16
|
|
|
17
17
|
## Example
|
|
18
18
|
|
|
@@ -401,41 +401,72 @@ const PresetOverlay = phx.frontend.createPresetDeviceNotSupported(SmallScreenNot
|
|
|
401
401
|
|
|
402
402
|
### Configuration Not Found Page
|
|
403
403
|
|
|
404
|
-
|
|
404
|
+
`phx.frontend.createConfigurationNotFoundPage` registers a page that replaces the one the configurator shows when it cannot open a configuration, as the layout element `ConfigurationNotFoundPage`. Without it, Phoenix renders its built-in "Configuration not accessible" page, which is the default `ConfigurationNotFoundPage` registration in `defaultConfiguratorElements`.
|
|
405
405
|
|
|
406
406
|
```typescript
|
|
407
407
|
// src/elements/ConfigurationNotFoundPage.tsx
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
<main>
|
|
415
|
-
<h1>We could not find this configuration</h1>
|
|
416
|
-
{props.onBack && <button onClick={props.onBack}>Start a new one</button>}
|
|
417
|
-
</main>
|
|
418
|
-
)
|
|
419
|
-
)
|
|
408
|
+
export default phx.frontend.createConfigurationNotFoundPage(({ onBack }) => (
|
|
409
|
+
<main>
|
|
410
|
+
<h1>We could not find this configuration</h1>
|
|
411
|
+
{onBack && <button onClick={onBack}>Start a new one</button>}
|
|
412
|
+
</main>
|
|
413
|
+
))
|
|
420
414
|
```
|
|
421
415
|
|
|
416
|
+
The helper types the page's props as `ConfigurationNotFoundPageProps` from `@salsita-ai/sdk/react`. A layout element registered under the name `ConfigurationNotFoundPage` with `createCustomPhoenixLayoutElement` replaces the page too, but its component is typed as taking only `children`.
|
|
417
|
+
|
|
422
418
|
- The page receives one optional prop, `onBack`, which returns to the start of the configurator. It is set when a configuration fails to load or is opened without a valid access key. It is not set on the start page itself, so a page that offers a way back shows it only when `onBack` is set.
|
|
423
419
|
- The configurator renders the element in four cases: a configuration fails to load, for example because its ID does not exist or the server rejects its access key; a configuration is opened without an access key for it; a signed-in user whose role may not create configurations (`acl.canCreate`) opens the start page; the preset screen has no preset category to show, for example because the host's `presetTags` match no preset. The last case happens inside the built-in preset screen, so a project that replaces it with `createPresetUi` decides what to show itself.
|
|
424
420
|
- The not-authenticated page, shown when session authentication fails or to an anonymous visitor who may not create a configuration, is its own element, which `createNotAuthenticatedPage` replaces. Its default, the `NotAuthenticatedPage` registration in `defaultConfiguratorElements`, renders the `ConfigurationNotFoundPage` element, so a project's not-found page shows there too.
|
|
425
421
|
- The admin's not-authorized page still shows the built-in page directly, not the element.
|
|
426
422
|
|
|
423
|
+
### Configurator Error Page
|
|
424
|
+
|
|
425
|
+
`phx.frontend.createConfiguratorErrorPage` registers a page that replaces the one the configurator shows in production when it cannot go on, as the layout element `ConfiguratorErrorPage`. Without it, Phoenix renders its built-in "Whoops, something went wrong!" page, which is the default `ConfiguratorErrorPage` registration in `defaultConfiguratorElements`.
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// src/elements/ConfiguratorErrorPage.tsx
|
|
429
|
+
export default phx.frontend.createConfiguratorErrorPage(({ reason, onRetry }) => (
|
|
430
|
+
<main>
|
|
431
|
+
<h1>{reason === 'presetCreationFailed' ? 'We could not start your design' : 'Something went wrong'}</h1>
|
|
432
|
+
<button onClick={onRetry}>{reason === 'presetCreationFailed' ? 'Reload' : 'Try again'}</button>
|
|
433
|
+
</main>
|
|
434
|
+
))
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
The helper types the page's props as `ConfiguratorErrorPageProps` from `@salsita-ai/sdk/react`. A layout element registered under the name `ConfiguratorErrorPage` with `createCustomPhoenixLayoutElement` replaces the page too, but its component is typed as taking only `children`.
|
|
438
|
+
|
|
439
|
+
- The page receives two props. `reason` says what failed: `uncaughtError` when rendering the configurator failed with an error nothing else handled, `presetCreationFailed` when starting a configuration from the only preset failed. `onRetry` tries again: after `uncaughtError` it renders the configurator again, after `presetCreationFailed` it reloads the page. The error itself is not passed; it is logged to the console.
|
|
440
|
+
- The configurator renders the element in two cases: an error reaches the configurator's error boundary, which wraps everything from the scene resources and the session down to the pages, including a failure while the application starts; and creating a configuration fails on a start screen with a single preset. The element is available in both, because the spec and its elements are part of the application before the configurator renders.
|
|
441
|
+
- A registered page that throws does not take the configurator down: the built-in page is shown in its place and the error is logged, until a hot reload replaces or removes the page.
|
|
442
|
+
- Three cases do not show the element. In development the configurator shows its detailed error pages instead. A failed preset creation with more than one preset is a snackbar on the preset screen (see [Loaded Configuration UI](#loaded-configuration-ui) for how a project shows it). The admin's own error page stays the built-in one, while the configurator the admin embeds renders the element.
|
|
443
|
+
|
|
444
|
+
### App Loader
|
|
445
|
+
|
|
446
|
+
While the configurator loads, Phoenix shows the app loader: an overlay over the scene's safe area. A layout element registered under the name `AppLoaderContent` replaces what the overlay shows. It receives `phases`, the loading phases running now, each with an `id` and an optional `label` and `progress`; the list can be empty while the loader waits for the scene to start loading. `createAppLoader` registers a component under that name with the same `phases`:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
// src/elements/AppLoader.tsx
|
|
450
|
+
export default phx.frontend.createAppLoader(({ phases }) => <span>{phases[0]?.label ?? 'Loading…'}</span>)
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
- Phoenix keeps the overlay, its place over the safe area and its fade-in; the element is only what it shows.
|
|
454
|
+
- The overlay stays up while a loading phase runs: the session, the project's data, the configuration and the 3D scene. It waits for the scene only when the loaded configuration's layout mounts a canvas, as the built-in layouts and `LayoutSlots.SharedCanvas` do. A `ConfiguratorLayout` without a canvas is not held by the loader: the overlay leaves once the configuration is up.
|
|
455
|
+
- The top-level `appLoader: legacy` key in the spec turns the overlay off.
|
|
456
|
+
- The built-in content is the default `AppLoaderContent` registration in `defaultConfiguratorElements`, see below.
|
|
457
|
+
|
|
427
458
|
### Loaded Configuration UI
|
|
428
459
|
|
|
429
460
|
A layout element registered under the name `ConfiguratorLayout` replaces the whole UI of a loaded configuration, in owner and in read mode, whatever `layout` the spec sets. Without it, Phoenix renders the layout the spec selects. [Custom Layout](?path=/docs/layouts-custom-layout--docs) is a worked example: a fully custom product page with its panel on the left, built from the scene, the safe-area hook and state hooks.
|
|
430
461
|
|
|
431
462
|
```typescript
|
|
432
463
|
// src/elements/ConfiguratorLayout.tsx
|
|
433
|
-
import { LayoutSlots
|
|
464
|
+
import { LayoutSlots } from '@salsita-ai/sdk/configurator'
|
|
434
465
|
|
|
435
466
|
const SIDEBAR_WIDTH = 320
|
|
436
467
|
|
|
437
468
|
export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', () => {
|
|
438
|
-
const { containerRef, safeAreaRef } = useSceneSafeArea()
|
|
469
|
+
const { containerRef, safeAreaRef } = phx.frontend.useSceneSafeArea()
|
|
439
470
|
|
|
440
471
|
return (
|
|
441
472
|
<div ref={containerRef} style={{ position: 'absolute', inset: 0 }}>
|
|
@@ -454,20 +485,20 @@ export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout
|
|
|
454
485
|
```
|
|
455
486
|
|
|
456
487
|
- `LayoutSlots.SharedCanvas` renders the scene. It is absolutely positioned and fills its nearest positioned ancestor, so the layout's root is positioned and covers the page. The layout positions its own UI above the canvas and after it in the markup, as the `aside` does; unpositioned content is painted under the canvas.
|
|
457
|
-
- `useSceneSafeArea()`, called without arguments, tells the camera which part of the canvas the UI leaves free. Attach `containerRef` to the element that covers the whole canvas and `safeAreaRef` to the region the scene should stay visible in: the hook measures the gap between the two and the camera frames the scene inside the safe area. Without it the scene is framed in the whole canvas. The safe-area element lies over the canvas, so it sets `pointerEvents: 'none'` to leave orbiting and clicking to the scene. A `canvasPadding` component in the spec scales the measured gaps, see [Canvas Padding](?path=/docs/layouts-canvas-padding--docs).
|
|
458
|
-
-
|
|
488
|
+
- `phx.frontend.useSceneSafeArea()`, called without arguments, tells the camera which part of the canvas the UI leaves free. Attach `containerRef` to the element that covers the whole canvas and `safeAreaRef` to the region the scene should stay visible in: the hook measures the gap between the two and the camera frames the scene inside the safe area. Without it the scene is framed in the whole canvas. The safe-area element lies over the canvas, so it sets `pointerEvents: 'none'` to leave orbiting and clicking to the scene. A `canvasPadding` component in the spec scales the measured gaps, see [Canvas Padding](?path=/docs/layouts-canvas-padding--docs). Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it.
|
|
489
|
+
- With `LayoutSlots.SharedCanvas` mounted, the app loader stays on screen until the scene has loaded. A layout without a canvas is not held by the loader, see [App Loader](#app-loader).
|
|
459
490
|
- The layout composes the rest from what `@salsita-ai/sdk/configurator` exports: `LayoutSlots.Services` and the other members of `LayoutSlots`, which [Layout Slots](?path=/docs/phoenix-layout-slots--docs) lists with what each shows, what feeds it, what it renders when the spec configures nothing for it and what it does in read mode.
|
|
460
491
|
- `LayoutSlots.DesktopSidebar` is the built-in desktop sidebar as one piece: the spec's tabs, back button, property pages and sidebar footer with their spacing, scrolling and empty states, and the assistant panel. Use it when the spec's property pages are what the customer should see and only their place changes, for example a sidebar on the left; build the controls yourself when they have to look like the brand's own. Without `width` it fills its container, so the layout positions and sizes that container; `miniSidebar` does not shrink it, so a layout that honours it reads `LayoutSlots.useSidebarContent().hasMiniSidebar` and lets the container take the sidebar's height. Its tabs sit outside it on the left; `side="start"` puts them on its right, for a sidebar on the left. In fullscreen it stays in place, transparent and inert, so a layout that wants the space back removes it while `phx.frontend.useIsFullscreenActive()` is set. See [Sidebar in a Custom Layout](?path=/docs/layouts-sidebar-in-a-custom-layout--docs).
|
|
461
|
-
- Nothing of the built-in layouts is rendered for it. A project layout finalizes over the spec's `finalizeAction` components, see the finalize paragraph of [Hooks for a Project Layout](#hooks-for-a-project-layout). The `finalizePage` and `finalizeButton` components are legacy: they cannot be declared in YAML, only in TypeScript, and `Legacy.CommonFinalizePage`, which is deprecated, shows a `finalizePage` for projects that still have one. `LayoutDialog` renders only what the project registers with `createLayoutDialog` and is not part of the finalize flow. The preview start screen is `DesktopPreview` or `MobilePreview`, which the layout shows while `usePreview().isPreview` is set. Both previews frame the scene themselves, so the layout renders the component that calls `useSceneSafeArea()` only while `isPreview` is not set; it then measures when it mounts after the preview. The assistant panel is available to a project layout only inside `LayoutSlots.DesktopSidebar`.
|
|
492
|
+
- Nothing of the built-in layouts is rendered for it. A project layout finalizes over the spec's `finalizeAction` components, see the finalize paragraph of [Hooks for a Project Layout](#hooks-for-a-project-layout). The `finalizePage` and `finalizeButton` components are legacy: they cannot be declared in YAML, only in TypeScript, and `Legacy.CommonFinalizePage`, which is deprecated, shows a `finalizePage` for projects that still have one. `LayoutDialog` renders only what the project registers with `createLayoutDialog` and is not part of the finalize flow. The preview start screen is `DesktopPreview` or `MobilePreview`, which the layout shows while `phx.frontend.usePreview().isPreview` is set. Both previews frame the scene themselves, so the layout renders the component that calls `phx.frontend.useSceneSafeArea()` only while `isPreview` is not set; it then measures when it mounts after the preview. The assistant panel is available to a project layout only inside `LayoutSlots.DesktopSidebar`.
|
|
462
493
|
- The layout reads the state it has to react to from hooks, listed with what each returns in [Hooks for a Project Layout](#hooks-for-a-project-layout).
|
|
463
|
-
- Messages for the user come one of two ways. `LayoutSlots.Services` shows violations of a rejected change, side-effect hints, errors and failed saves as the built-in snackbars, which render into `LayoutSlots.DesktopSnackbarsRoot` or `LayoutSlots.MobileSnackbarsRoot` wherever the layout mounts it. `phx.frontend.useConfiguratorMessages(onMessage)` hands the same messages to the layout's own UI instead, with their text resolved, as the toast in [Custom Layout](?path=/docs/layouts-custom-layout--docs#show-messages) does; mount one of the two, not both, or every message is shown twice. A failure to save the configuration, and the first successful save after it, are messages too, so a layout that mounts neither shows no sign of a failed save. An error of the built-in finalize and other actions stays a snackbar either way. It renders into the snackbar root, so a layout that mounts none shows it outside the configurator, in the page's `body`. A failure to start a configuration from a preset is not a message of the hook either: it happens on the preset screen, before any `ConfiguratorLayout` mounts. In production with more than one preset it is a snackbar there, also on a preset screen replaced with `createPresetUi` (see [Preset UI](#preset-ui)), which reads the error from `phx.frontend.usePresetSelection().selectionError` and mounts `PresetSnackbarsRoot` to keep the snackbar inside the configurator. A finalize action the layout runs with `phx.frontend.useFinalizeActions` or `phx.frontend.useFinalizeAction` reports its error as the action's `error` instead, neither as a message nor as a snackbar.
|
|
494
|
+
- Messages for the user come one of two ways. `LayoutSlots.Services` shows violations of a rejected change, side-effect hints, errors and failed saves as the built-in snackbars, which render into `LayoutSlots.DesktopSnackbarsRoot` or `LayoutSlots.MobileSnackbarsRoot` wherever the layout mounts it. `phx.frontend.useConfiguratorMessages(onMessage)` hands the same messages to the layout's own UI instead, with their text resolved, as the toast in [Custom Layout](?path=/docs/layouts-custom-layout--docs#show-messages) does; mount one of the two, not both, or every message is shown twice. A failure to save the configuration, and the first successful save after it, are messages too, so a layout that mounts neither shows no sign of a failed save. An error of the built-in finalize and other actions stays a snackbar either way. It renders into the snackbar root, so a layout that mounts none shows it outside the configurator, in the page's `body`. A failure to start a configuration from a preset is not a message of the hook either: it happens on the preset screen, before any `ConfiguratorLayout` mounts. In production with more than one preset it is a snackbar there, also on a preset screen replaced with `createPresetUi` (see [Preset UI](#preset-ui)), which reads the error from `phx.frontend.usePresetSelection().selectionError` and mounts `PresetSnackbarsRoot` to keep the snackbar inside the configurator. With a single preset it is the [Configurator Error Page](#configurator-error-page). A finalize action the layout runs with `phx.frontend.useFinalizeActions` or `phx.frontend.useFinalizeAction` reports its error as the action's `error` instead, neither as a message nor as a snackbar.
|
|
464
495
|
- Any project layout element named `ConfiguratorLayout` takes over the page.
|
|
465
496
|
|
|
466
|
-
`defaultConfiguratorElements`, exported from `@salsita-ai/sdk/configurator`, holds the built-in UI as a `ConfiguratorLayout` registration that renders the layout the spec selects, the built-in not-found page as a `ConfigurationNotFoundPage` registration, the built-in not-authenticated page as a `NotAuthenticatedPage` registration,
|
|
497
|
+
`defaultConfiguratorElements`, exported from `@salsita-ai/sdk/configurator`, holds the built-in UI as a `ConfiguratorLayout` registration that renders the layout the spec selects, the built-in not-found page as a `ConfigurationNotFoundPage` registration, the built-in not-authenticated page as a `NotAuthenticatedPage` registration, the built-in preset screen as a `PresetUi` registration, the built-in error page as a `ConfiguratorErrorPage` registration, and the app loader's built-in content as an `AppLoaderContent` registration. Generated code registers these defaults with `withDefaultElements`, apart from the project's elements, so the built-in UI is a registration like any other; a project element registered under the same name wins over a default. The list also holds the configurator's built-in controls and layout elements under the kinds the spec names them by, such as `attributeSelect` or `undoToolbarButton`, so a project element registered under one of those names replaces the built-in one wherever the spec names that kind. A config assembled by hand, without generated code, calls `withDefaultElements(defaultConfiguratorElements)` too; the lists are the only way the built-in elements are registered, so without it the page shows a "not found" box instead of the configurator, and a project layout shows one in place of every built-in control. The admin mounts a full configurator, so its list, `defaultAdminElements` from `@salsita-ai/sdk/admin`, holds the configurator's defaults followed by the admin's built-in pages, sidebars and actions; generated admin code passes it to `withDefaultElements` in place of `defaultConfiguratorElements`, because each call replaces the defaults passed before.
|
|
467
498
|
|
|
468
499
|
#### Hooks for a Project Layout
|
|
469
500
|
|
|
470
|
-
A project layout reads the product and the configurator's state through hooks, and changes them through the functions the hooks return.
|
|
501
|
+
A project layout reads the product and the configurator's state through hooks, and changes them through the functions the hooks return. Every hook in these tables is a member of `phx.frontend` and is also exported from `@salsita-ai/sdk/react` under the same name, unless its row names another export. No hook looks at read mode: the control hooks and the navigation and fullscreen setters keep working while `accessInfo.viewMode` is `true`, so the layout decides what a viewer can change.
|
|
471
502
|
|
|
472
503
|
**The product**
|
|
473
504
|
|
|
@@ -490,7 +521,7 @@ The other control hooks in [Controls](#controls) take the same two arguments, `u
|
|
|
490
521
|
|
|
491
522
|
**Texts**
|
|
492
523
|
|
|
493
|
-
Texts from the spec, such as option titles and the `detail` fields, are `Localizable`, not `string`. `useResolveLocalizable()`
|
|
524
|
+
Texts from the spec, such as option titles and the `detail` fields, are `Localizable`, not `string`. `phx.frontend.useResolveLocalizable()` returns a function that turns one into a string in the current language, which suits a list; `phx.frontend.useLocalizable(text)` and `<LocalizableMessage>` from `@salsita-ai/sdk/react` resolve a single text, see [Localization](?path=/docs/phoenix-localization--docs).
|
|
494
525
|
|
|
495
526
|
**Mode, fullscreen, preview and navigation**
|
|
496
527
|
|
|
@@ -498,7 +529,8 @@ Texts from the spec, such as option titles and the `detail` fields, are `Localiz
|
|
|
498
529
|
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
499
530
|
| `phx.frontend.useAccessService()` | `{ configurationId, accessInfo }`. `accessInfo.viewMode` is `true` while a configuration shared for viewing is open, and also while the host sets the `readonly` property. |
|
|
500
531
|
| `phx.frontend.useIsFullscreenActive()` and `phx.frontend.useSetFullscreen()` | The fullscreen flag, a `boolean`, and a function `(enabled: boolean) => void` that sets it. |
|
|
501
|
-
| `usePreview()`
|
|
532
|
+
| `phx.frontend.usePreview()` | `{ isPreview, isExpanded, isPreviewEnabled, isHidden, enter, leave }`. The layout shows a preview start screen while `isPreview` is `true`. Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it, not `@salsita-ai/sdk/react`. |
|
|
533
|
+
| `phx.frontend.useCloseAction()` | The host's close action, `() => Promise<void>`, or `null` when the integration mode offers none. It dispatches the web component's `close` event and, depending on the integration mode, first leaves for the go-back URL of the page's query or puts the configurator on standby. Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it, not `@salsita-ai/sdk/react`. |
|
|
502
534
|
| `phx.frontend.useActiveNavigationState()` | The active navigation state, one of the spec's `navigationStates`. |
|
|
503
535
|
| `phx.frontend.usePushNavigate()` and `phx.frontend.usePopNavigate()` | `(state) => void`, which enters a state and remembers the active one, and `() => void`, which returns to the remembered state and does nothing when there is none. `phx.frontend.useNavigate()` returns `(state) => void`, which enters a state and forgets the history. |
|
|
504
536
|
| `phx.frontend.useActiveViewState()` and `phx.frontend.useActiveToolState()` | The active view state and tool state. |
|
|
@@ -507,7 +539,7 @@ Texts from the spec, such as option titles and the `detail` fields, are `Localiz
|
|
|
507
539
|
| `phx.frontend.useUndoRedo()` | `{ canUndo, canRedo, undo, redo }`: whether the undo history has a change to undo or to redo, and two `() => void` functions that undo or redo it, as the toolbar's undo and redo buttons do, pushing the same analytics events; each does nothing while there is nothing to undo or redo. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
|
|
508
540
|
| `phx.frontend.useTabNavigation(options?)` | `{ source, tabs, activeTab, select }` for the spec's tabs: `source` is `'sidebarTabs'` (every active `sidebarTabs` of the selected entity, in spec order) or `'horizontalTabs'` (the first active `horizontalTabs`), or `null` without tabs. Each tab is `{ index, label, icon, iconNode, navigationStates, state, isActive }`, with the label resolved, `icon` a PhxIcon name and `iconNode` a React node given by TypeScript spec code; `activeTab` is the first active tab or `null`. `select(index or navigation state)` enters the tab's first navigation state, as the built-in tabs do, pushing the same analytics event for `horizontalTabs`. `options.source` (`'auto'`, `'sidebarTabs'` or `'horizontalTabs'`) chooses the kind; `'auto'` prefers `sidebarTabs` when both are active, with a warning in development. `options.entityId` reads another entity's `sidebarTabs`. A tab without navigation states is left out, with a warning in development. It does not check read mode. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
|
|
509
541
|
| `phx.frontend.useStepNavigation()` | `{ steps, activeStep, activeIndex, isFirst, isLast, canGoNext, canGoPrevious, next, previous, goTo }` for the spec's `navigationSteps`, read from the first active instance on the root entity, the one the built-in layout shows. Each step is `{ index, label, navigationStates, state, status }`, with the label resolved, `state` the navigation state that enters the step and `status` `'complete'`, `'active'` or `'upcoming'`; while the active navigation state belongs to no step, `activeStep` is `null`, `activeIndex` is -1, every step is `'upcoming'` and the next step is the first one. A single step is both first and last. `next()` and `previous()` enter the following or preceding step, as the built-in step buttons do, pushing the same analytics events; `goTo(index or navigation state)` enters any step, as the header's stepper does, pushing its event. Each does nothing without a step to enter. A step without navigation states is left out, with a warning in development. It enforces no order and does not check read mode. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
|
|
510
|
-
| `useActiveViewports()`
|
|
542
|
+
| `phx.frontend.useActiveViewports()` | The responsive breakpoints the configurator's container satisfies, as `Viewport[]`, or `null` before it is measured. It follows the container, not the window, so it is the switch between a desktop and a phone layout. `desktop` is a container wider than 768 pixels and `mobile` one at most 768 pixels wide; `phone` is narrower than 480 and `tablet` between 480 and 1024 pixels; `landscape` and `portrait` follow the browser window's orientation. The built-in layout uses its phone arrangement unless `desktop` is active. |
|
|
511
543
|
|
|
512
544
|
**Steps and tabs**
|
|
513
545
|
|
|
@@ -26,8 +26,8 @@ export const Toolbar = () => (
|
|
|
26
26
|
|
|
27
27
|
- A slot takes no props unless its row names them, and reads what it shows from the spec and the configurator's state. It renders inside the configurator only, under a `ConfiguratorLayout`.
|
|
28
28
|
- A slot brings its own look but not its place: it adds no padding or scrolling around itself, so the layout puts it in a container. Two members differ: `SharedCanvas` fills its nearest positioned ancestor, and `DesktopSidebar` brings the sidebar's spacing and scrolling with it.
|
|
29
|
-
- Read mode means `useAccessService().accessInfo.viewMode` is `true`: a configuration shared for viewing is open, or the host sets the `readonly` property. "No change" in the read-mode column means the slot renders the same for a viewer, so the layout decides whether a viewer sees it.
|
|
30
|
-
- `Desktop*` and `Mobile*` members are twins that differ in size and arrangement; [Phone layout](#phone-layout) lists the phone ones. The container's width, not the window's, decides which one fits: `useActiveViewports()
|
|
29
|
+
- Read mode means `phx.frontend.useAccessService().accessInfo.viewMode` is `true`: a configuration shared for viewing is open, or the host sets the `readonly` property. "No change" in the read-mode column means the slot renders the same for a viewer, so the layout decides whether a viewer sees it.
|
|
30
|
+
- `Desktop*` and `Mobile*` members are twins that differ in size and arrangement; [Phone layout](#phone-layout) lists the phone ones. The container's width, not the window's, decides which one fits: `phx.frontend.useActiveViewports()`, also exported from `@salsita-ai/sdk/react`, returns the breakpoints the container satisfies. The built-in layout uses the `Desktop*` members while `desktop` is active, for a container wider than 768 pixels, and the `Mobile*` members otherwise, while `mobile` is active; `phone` means narrower than 480 pixels and `tablet` between 480 and 1024.
|
|
31
31
|
|
|
32
32
|
## Canvas and services
|
|
33
33
|
|
|
@@ -40,7 +40,7 @@ export const Toolbar = () => (
|
|
|
40
40
|
| `SideEffects` | An info snackbar for each committed update that carries a hint | The hints of committed updates | Nothing | No change |
|
|
41
41
|
| `DesktopSnackbarsRoot` | The element the snackbars are rendered into: a stack anchored to its bottom edge, at most 320 pixels wide | Every snackbar the configurator raises | An empty element | No change |
|
|
42
42
|
|
|
43
|
-
- Mount `SharedCanvas`, not `Canvas`: `Canvas` suspends while it probes the GPU on its first mount, and `SharedCanvas` holds the boundary that catches it.
|
|
43
|
+
- Mount `SharedCanvas`, not `Canvas`: `Canvas` suspends while it probes the GPU on its first mount, and `SharedCanvas` holds the boundary that catches it. `SharedCanvas` also tells the app loader to wait for the scene from the moment it mounts; `Canvas` alone does so only once the probe is done, so the loader can leave and come back. A layout with neither is not held by the loader for a scene.
|
|
44
44
|
- `SharedCanvas` fills its nearest positioned ancestor and paints an opaque background, so the layout positions its own UI after it in the markup.
|
|
45
45
|
- Snackbars are raised by `Services`, for violations, side-effect hints, validation errors and a failed save, and also by the built-in finalize and footer actions when their handler fails. A layout that shows these messages its own way calls `phx.frontend.useConfiguratorMessages(onMessage)` instead of mounting `Services`, and then shows the failed save too, see [Configurator Interface → Hooks for a Project Layout](?path=/docs/phoenix-configurator-interface--docs#hooks-for-a-project-layout); the other snackbars still need a root. They render into the snackbar root the layout mounts; with no root mounted they render into the page's `document.body`, outside the configurator. The desktop root is a zero-height element the snackbars stack upwards from, so the layout places it where their bottom edge belongs. The phone root leaves them fixed to the viewport, see `MobileSnackbarsRoot`. A finalize action the layout runs with `phx.frontend.useFinalizeActions` or `phx.frontend.useFinalizeAction` raises no snackbar; its error is the action's `error`.
|
|
46
46
|
|
|
@@ -79,7 +79,7 @@ Instead of `DesktopViewControls` or `MobileViewControls`, a layout can build its
|
|
|
79
79
|
| `DesktopReadonlyBanner` | "You're viewing a read-only configuration" with an **Edit configuration** button that opens `CloneToEditDialog`. Props: the MUI `Alert` props and `icon`, also settable in the theme as `PhxDesktopReadonlyBanner` | `useIsCloningAllowed()`; its icon from the application config's `readonlyBannerIcon` | Nothing unless cloning is allowed | **It does not check read mode.** The layout renders it only in read mode, as the built-in layout does while not in fullscreen |
|
|
80
80
|
| `MobileReadonlyBanner` | An **Edit** button that opens `CloneToEditDialog`. Props: the MUI `Button` props and `icon`, also settable in the theme as `PhxMobileReadonlyBanner` | As `DesktopReadonlyBanner` | Nothing unless cloning is allowed | As `DesktopReadonlyBanner` |
|
|
81
81
|
| `CloneToEditDialog` | The "Edit configuration" dialog that creates an editable copy of the configuration and opens it. Props, typed `CloneToEditDialogProps`: `open` and `onClose` | The open configuration | — | — |
|
|
82
|
-
| `useIsCloningAllowed()` | A hook, `boolean`: `true` when the application config sets `shareCloneToEdit` and the session's role may create configurations
|
|
82
|
+
| `useIsCloningAllowed()` | A hook, also `phx.frontend.useIsCloningAllowed()`, returning a `boolean`: `true` when the application config sets `shareCloneToEdit` and the session's role may create configurations | The application config and the session | `false` | Does not check read mode |
|
|
83
83
|
| `DesktopPoweredBy` / `MobilePoweredBy` | The "Powered by Salsita" mark, a link | Its props `visible`, `variant` (`'black'` or `'white'`), `href`, `title`, `slots` and `slotProps`. All but `href` and `title` are also settable in the theme as `PhxDesktopBrandingPoweredBy` / `PhxMobileBrandingPoweredBy` | The mark | No change |
|
|
84
84
|
|
|
85
85
|
## Page footer
|
|
@@ -34,8 +34,8 @@ handler, element and transformer; it reads the frontend environment when it is i
|
|
|
34
34
|
uses it stubs `phx.frontendEnv` first.
|
|
35
35
|
|
|
36
36
|
A test that mounts the configurator's UI builds from `configuratorConfig`: it carries the SDK's default
|
|
37
|
-
elements, the built-in layout, preset screen, not-found
|
|
38
|
-
buttons and sidebar-footer pieces, which `configuratorBuilder` does not, so a test that mounts the UI from `createTestApplicationConfig(configuratorBuilder)`
|
|
37
|
+
elements, the built-in layout, preset screen, not-found, not-authenticated and error pages, app loader content,
|
|
38
|
+
controls, toolbar buttons and sidebar-footer pieces, which `configuratorBuilder` does not, so a test that mounts the UI from `createTestApplicationConfig(configuratorBuilder)`
|
|
39
39
|
shows a "not found" box in place of every built-in element. Code that assembles a config without the
|
|
40
40
|
generated files calls `.withDefaultElements(defaultConfiguratorElements)` on its configurator builder, with
|
|
41
41
|
the list from `@salsita-ai/sdk/configurator`, or `defaultAdminElements` from `@salsita-ai/sdk/admin` for
|
package/dist/react-sdk/application-runtime/common-services/analytics/frontend-analytics.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export declare const FrontendAnalyticsFactory: {
|
|
|
28
28
|
FinalizeButtonClicked: typeof FinalizeButtonClicked;
|
|
29
29
|
SummaryButtonClicked: typeof SummaryButtonClicked;
|
|
30
30
|
TopNavigationStepClicked: typeof TopNavigationStepClicked;
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated The SDK never pushes this event: a click on a horizontal tab pushes `TopNavigationStepClicked`.
|
|
33
|
+
* Use `TopNavigationStepClicked` instead. This API will be removed in the breaking release before v1.
|
|
34
|
+
*/
|
|
31
35
|
HorizontalTabClicked: typeof HorizontalTabClicked;
|
|
32
36
|
GoToProductsClicked: typeof GoToProductsClicked;
|
|
33
37
|
ProductSelected: typeof ProductSelected;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './use-configurator-messages.js';
|
|
2
|
+
export * from './use-domain-context.js';
|
|
3
|
+
export * from './use-entity-selector.js';
|
|
4
|
+
export * from './use-observable-selector.js';
|
|
5
|
+
export * from './use-slices.js';
|
|
6
|
+
export * from './use-state-slice.js';
|
|
7
|
+
export * from './use-state-validity.js';
|
|
8
|
+
export * from './use-store-selector.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./use-configurator-messages.js";export*from"./use-domain-context.js";export*from"./use-entity-selector.js";export*from"./use-observable-selector.js";export*from"./use-slices.js";export*from"./use-state-slice.js";export*from"./use-state-validity.js";export*from"./use-store-selector.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './use-component.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./use-component.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useMemo as t}from"react";import{useApplicationRuntimeSelector as e}from"../../application-runtime/index.js";import{useDomainContextSelectors as n
|
|
1
|
+
import{useMemo as t}from"react";import{useApplicationRuntimeSelector as e}from"../../application-runtime/index.js";import{useDomainContextSelectors as n,useRenderedSelector as o,useStoreSelector as r}from"../common-hooks/index.js";let s=t=>[t.componentType,t.entityId,t.instanceName,t.componentTypes?.length,...t.componentTypes??[],t.entityIds?.length,...t.entityIds??[],t.instanceNames?.length,...t.instanceNames??[]],a=o=>{let r=n(),{componentReader:a}=r.getDomainSystems(),p=t(()=>a.isComponentTypeAttached(o),[a,e(r.getComponentReaderIndexes),...s(o)]);return{componentReader:a,isActive:p}};export const useComponent=t=>{let{entityId:e,componentType:n,instanceName:o}=t,{componentReader:s,isActive:p}=a({entityId:e,componentType:n,instanceName:o}),m=r(t=>s.readOneComponent({entityId:e,componentType:n,instanceName:o},t.state),[s,e,n,o],p);return null===m?null:m?.state};export const useRenderedComponent=t=>{let{entityId:e,componentType:n,instanceName:r}=t,{componentReader:s,isActive:p}=a({entityId:e,componentType:n,instanceName:r}),m=o(t=>s.readOneComponent({entityId:e,componentType:n,instanceName:r},t.state),[s,e,n,r],p);return null===m?null:m?.state};export const useComponents=t=>{let{entityIds:e,componentTypes:n,instanceNames:o}=t,{componentReader:p,isActive:m}=a({entityIds:e,componentTypes:n,instanceNames:o});return r(t=>p.readManyComponents({entityIds:e,componentTypes:n,instanceNames:o},t.state),[p,...s({entityIds:e,componentTypes:n,instanceNames:o})],m)};export const useDynamicComponents=t=>{let{entityIds:e,componentTypes:n,instanceNames:o}=t,{componentReader:s,isActive:p}=a({entityIds:e,componentTypes:n,instanceNames:o});return r(t=>s.readManyComponents({entityIds:e,componentTypes:n,instanceNames:o},t.state),[s,n,e,o],p)};export const useRenderedComponents=t=>{let{entityIds:e,componentTypes:n,instanceNames:r}=t,{componentReader:p,isActive:m}=a({entityIds:e,componentTypes:n,instanceNames:r});return o(t=>p.readManyComponents({entityIds:e,componentTypes:n,instanceNames:r},t.state),[p,...s({entityIds:e,componentTypes:n,instanceNames:r})],m)};export const useFirstComponent=t=>{let{entityIds:e,componentTypes:n,instanceNames:o}=t,{componentReader:p,isActive:m}=a({entityIds:e,componentTypes:n,instanceNames:o});return r(t=>p.readFirstComponent({entityIds:e,componentTypes:n,instanceNames:o},t.state),[p,...s({entityIds:e,componentTypes:n,instanceNames:o})],m)};export const useFirstRenderedComponent=t=>{let{entityIds:e,componentTypes:n,instanceNames:r}=t,{componentReader:p,isActive:m}=a({entityIds:e,componentTypes:n,instanceNames:r});return o(t=>p.readFirstComponent({entityIds:e,componentTypes:n,instanceNames:r},t.state),[p,...s({entityIds:e,componentTypes:n,instanceNames:r})],m)};export const useRootComponents=e=>{let n=t(()=>Array.isArray(e)?e:[e],[e]),{componentReader:o,isActive:p}=a({componentTypes:n});return r(t=>o.readManyComponents({componentTypes:n,entityIds:[t.state.rootEntityId]},t.state),[o,...s({componentTypes:n})],p)};export const useRootRenderedComponents=e=>{let n=t(()=>Array.isArray(e)?e:[e],[e]),{componentReader:r,isActive:p}=a({componentTypes:n});return o(t=>r.readManyComponents({componentTypes:n,entityIds:[t.state.rootEntityId]},t.state),[r,...s({componentTypes:n})],p)};export const useFirstRootComponent=e=>{let n=t(()=>Array.isArray(e)?e:[e],[e]),{componentReader:o,isActive:p}=a({componentTypes:n});return r(t=>o.readFirstRootComponent({componentTypes:n},t.state),[o,...s({componentTypes:n})],p)};export const useFirstRootRenderedComponent=e=>{let n=t(()=>Array.isArray(e)?e:[e],[e]),{componentReader:r,isActive:p}=a({componentTypes:n});return o(t=>r.readFirstRootComponent({componentTypes:n},t.state),[r,...s({componentTypes:n})],p)};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useDomain as t
|
|
1
|
+
import{useDomain as t,useRenderedSelector as e,useStoreSelector as n}from"../common-hooks/index.js";export const useDomainEntityState=e=>{let{domain:o}=t();return n(t=>o.get$Entity(e,t.state),[o,e])};export const useDomainEntityRenderedState=n=>{let{domain:o}=t();return e(t=>o.get$Entity(n,t.state),[o,n])};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useLingui as t}from"@lingui/react";import{priceComponent as e}from"#package/domain";import{useFirstComponent as
|
|
1
|
+
import{useLingui as t}from"@lingui/react";import{priceComponent as e}from"#package/domain";import{useFirstComponent as r}from"../component-hooks/index.js";import{useRootEntityId as o}from"./use-root-entity-id.js";export const useEntityPrice=o=>{let n=r({componentTypes:[e],entityIds:[o]}),{i18n:i}=t(),c=n?.state?.currency??"USD",s=n?.state.price;return void 0===s?null:i.number(s,{style:"currency",currency:c})};export const useRootEntityPrice=()=>{let t=o();return useEntityPrice(t)};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRenderedSelector as t,useStoreSelector as o}from"../common-hooks/
|
|
1
|
+
import{useRenderedSelector as t,useStoreSelector as o}from"../common-hooks/index.js";export const useRootEntityId=()=>o(t=>t.state.rootEntityId,[]);export const useRootEntityRenderedId=()=>t(t=>t.state.rootEntityId,[]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useDomain as t
|
|
1
|
+
import{useDomain as t,useRenderedSelector as e,useStoreSelector as o}from"../common-hooks/index.js";export const useRootEntityState=()=>{let{domain:e}=t();return o(t=>e.getRoot$Entity(t.state),[e])};export const useRootEntityRenderedState=()=>{let{domain:o}=t();return e(t=>o.getRoot$Entity(t.state),[o])};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useDomain as o
|
|
1
|
+
import{useDomain as o,useRenderedSelector as e}from"../common-hooks/index.js";export const useComposition=()=>{let{compositionProjections:t}=o();return e(o=>t.getTree(o.state),[t])};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback as t}from"react";import{useDomain as e
|
|
1
|
+
import{useCallback as t}from"react";import{useDomain as e,useStoreSelector as o}from"../common-hooks/index.js";export const useDimensionConfig=()=>{let{dimensionSystem:t}=e();return o(e=>t.getDimensionConfig(e.state),[t])};export const useDimensionState=()=>{let{dimensionSystem:t}=e();return o(e=>t.state.get(e.state),[t])};export const useDimensions=()=>{let{dimensionSystem:o}=e(),{storeSystem:s}=e();return{setGlobalUnit:t(t=>{let e=o.getSetGlobalUnitUpdates(t,s.stateSlices.committed.value.state);s.stageAndCommit(e)},[o,s])}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useDomain as t
|
|
1
|
+
import{useDomain as t,useRenderedSelector as e}from"../common-hooks/index.js";export const useHoveredDomainEntityId=()=>{let{navigationSystem:o}=t();return e(t=>o.getActiveHoverState(t.state),[o])};export const useHoveredEntityId=t=>{let e=useHoveredDomainEntityId();return e&&t?.isId(e)?e:null};export const useHoveredEntity=o=>{let n=useHoveredEntityId(o),{domain:r}=t();return e(t=>n?r.get$Entity(n,t.state):null,[r,n])};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback as t}from"react";import{useDomain as e
|
|
1
|
+
import{useCallback as t}from"react";import{useDomain as e,useStoreSelector as a}from"../common-hooks/index.js";export const useActiveNavigationState=()=>{let{navigationSystem:t}=e();return a(e=>t.getActiveNavigationState(e.state),[t])};export const useActiveViewState=()=>{let{navigationSystem:t}=e();return a(e=>t.getActiveViewState(e.state),[t])};export const useSetViewState=()=>{let{storeSystem:a,navigationSystem:o}=e();return t(t=>{a.stageAndCommit([o.getViewUpdate(t)])},[a,o])};export const useActiveToolState=()=>{let{navigationSystem:t}=e();return a(e=>t.getActiveToolState(e.state),[t])};export const useIsFullscreenActive=()=>{let{navigationSystem:t}=e();return a(e=>t.getActiveFullscreenState(e.state),[t])};export const useSetFullscreen=()=>{let{storeSystem:a,navigationSystem:o}=e();return t(t=>{a.stageAndCommit([o.getFullscreenUpdate(t)])},[a,o])};export const useDraggedDomainEntityId=()=>{let{navigationSystem:t}=e();return a(e=>t.getActiveDragState(e.state),[t])};export const useActiveViewports=()=>{let{navigationSystem:t}=e();return a(e=>t.getActiveViewportState(e.state),[t])};export const useNavigate=()=>{let{storeSystem:a,navigationSystem:o}=e();return t(t=>{a.stageAndCommit([o.getNavigationUpdate(t)])},[a,o])};export const usePushNavigate=()=>{let{storeSystem:a,navigationSystem:o}=e();return t(t=>{a.stageAndCommit([o.getPushNavigationUpdate(t,a.stateSlices.committed.value.state)])},[a,o])};export const usePopNavigate=()=>{let{storeSystem:a,navigationSystem:o}=e();return t(()=>{let t=o.getPopNavigationUpdate(a.stateSlices.committed.value.state);t&&a.stageAndCommit([t])},[a,o])};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback as e}from"react";import{useAnalyticsService as t}from"../../application-runtime/index.js";import{useDomain as
|
|
1
|
+
import{useCallback as e}from"react";import{useAnalyticsService as t}from"../../application-runtime/index.js";import{useDomain as n,useStoreSelector as r}from"../common-hooks/index.js";export const useSelectedDomainEntityId=()=>{let{navigationSystem:e}=n();return r(t=>e.getActiveSelectionState(t.state),[e])};export const useSelectedEntityId=e=>{let t=useSelectedDomainEntityId();return e?.isId(t)?t:null};export const useSelectEntity=r=>{let{storeSystem:s,navigationSystem:o}=n(),i=t();return e(e=>{s.stageAndCommit([o.getSelectUpdate(e)]),r&&i.push(r(e))},[s,o,r,i])};export const useSelectParent=(r,s,o)=>{let{storeSystem:i,navigationSystem:l,compositionProjections:c}=n(),a=t();return e(()=>{let e=c.getTree(i.stateSlices.committed.value.state).map.get(r);if(e?.parent){i.stageAndCommit([l.getSelectUpdate(e.parent.id)]),o&&a.push(o(e.parent.id));return}s&&(i.stageAndCommit([l.getSelectUpdate(s)]),o&&a.push(o(s)))},[i,l,r,c,s,o,a])};export const useDeselect=r=>{let{storeSystem:s,navigationSystem:o}=n(),i=t();return e(()=>{s.stageAndCommit([o.getDeselectUpdate()]),r&&i.push(r())},[s,o,r,i])};export const useSelectedEntity=e=>{let t=useSelectedEntityId(e),{domain:s}=n();return r(e=>t?s.get$Entity(t,e.state):null,[s,t])};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback as t,useEffect as
|
|
1
|
+
import{useCallback as t,useEffect as a,useMemo as o}from"react";import{horizontalTabsComponent as e,sidebarTabsComponent as i}from"#package/domain";import{FrontendAnalyticsFactory as n,useAnalyticsService as s,useIsDev as r}from"../../application-runtime/index.js";import{useResolveLocalizable as l}from"../../localizable/index.js";import{useDomain as m}from"../common-hooks/index.js";import{useComponents as p}from"../component-hooks/index.js";import{useActiveNavigationState as c}from"./navigation-hooks.js";import{useSelectedDomainEntityId as b}from"./selection-hooks.js";let h=(t,o)=>{let e=r();a(()=>{e&&t&&console.warn(o)},[e,t,o])};export const useTabNavigation=(a={})=>{let{source:r="auto",entityId:v}=a,g=c(),d=b(),f=l(),T=s(),{navigationSystem:u,storeSystem:z}=m(),S=p({componentTypes:[i],entityIds:[v??d]}),j=p({componentTypes:[e]}),N="horizontalTabs"!==r&&S.length>0,k="sidebarTabs"!==r&&j.length>0,x=N?"sidebarTabs":k?"horizontalTabs":null;h(N&&k,"Both sidebarTabs and horizontalTabs are active, so useTabNavigation shows the sidebarTabs. Remove one of them from the spec, or pass `source` to choose."),h("horizontalTabs"===x&&j.length>1,"Several horizontalTabs are active, so useTabNavigation shows the first one. Keep one active, for example with $filter.");let y=o(()=>"sidebarTabs"===x?S.flatMap(t=>t.state.tabs):"horizontalTabs"===x?j[0]?.state.tabs??[]:[],[x,S,j]),w=y.filter(t=>0===t.navigationStates.length);h(w.length>0,`The ${x} tabs ${w.map(t=>`"${f(t.label)}"`).join(", ")} have no navigation states, so useTabNavigation leaves them out. Give each tab at least one navigation state.`);let $=o(()=>y.flatMap(t=>{let a=t.navigationStates[0];return void 0===a?[]:[{tab:t,state:a}]}).map(({tab:t,state:a},o)=>({index:o,label:f(t.label),icon:"string"==typeof t.icon?t.icon:void 0,iconNode:"string"==typeof t.icon||null===t.icon?void 0:t.icon,navigationStates:t.navigationStates,state:a,isActive:t.navigationStates.includes(g)})),[y,g,f]),A=$.find(t=>t.isActive)??null,C=t(t=>{let a="number"==typeof t?$[t]:$.find(a=>a.navigationStates.includes(t));a&&("horizontalTabs"===x&&T.push(n.TopNavigationStepClicked(a.state)),z.stageAndCommit([u.getNavigationUpdate(a.state)]))},[$,x,T,z,u]);return{source:x,tabs:$,activeTab:A,select:C}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback as o}from"react";import{FrontendAnalyticsFactory as
|
|
1
|
+
import{useCallback as o}from"react";import{FrontendAnalyticsFactory as n,useAnalyticsService as e}from"../../application-runtime/index.js";import{useDomain as d,useEagerObservableValue as t}from"../common-hooks/index.js";export const useUndoRedo=()=>{let r=e(),{undoSystem:i}=d(),{canUndo:c,canRedo:m}=t(i.can$);return{canUndo:c,canRedo:m,undo:o(()=>{i.canUndo()&&(r.push(n.UndoButtonClicked()),i.undo())},[r,i]),redo:o(()=>{i.canRedo()&&(r.push(n.RedoButtonClicked()),i.redo())},[r,i])}};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why the configurator shows its error page:
|
|
3
|
+
* - `uncaughtError`: rendering the configurator failed with an error nothing else handled;
|
|
4
|
+
* - `presetCreationFailed`: starting a configuration from a preset failed on a start screen that has
|
|
5
|
+
* no preset grid to return to (a single preset).
|
|
6
|
+
*/
|
|
7
|
+
export type ConfiguratorErrorReason = 'uncaughtError' | 'presetCreationFailed';
|
|
8
|
+
/**
|
|
9
|
+
* The props of the `ConfiguratorErrorPage` element, the page the configurator shows in production
|
|
10
|
+
* when it cannot go on; a project registers its page with `createConfiguratorErrorPage`. In
|
|
11
|
+
* development the configurator shows its detailed error pages instead.
|
|
12
|
+
*/
|
|
13
|
+
export interface ConfiguratorErrorPageProps {
|
|
14
|
+
readonly reason: ConfiguratorErrorReason;
|
|
15
|
+
/**
|
|
16
|
+
* Tries again: after an `uncaughtError` it renders the configurator again, after
|
|
17
|
+
* `presetCreationFailed` it reloads the page.
|
|
18
|
+
*/
|
|
19
|
+
readonly onRetry: () => void;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
export type { ConfiguratorErrorPageProps, ConfiguratorErrorReason } from './configurator-error-page-props.js';
|
|
1
2
|
export { PhoenixEntityElement, PhoenixEntityElements, PhoenixEntityElementWithChildren, type PhoenixEntityElementProps, type PhoenixEntityElementsProps, type PhoenixEntityElementWithChildrenProps, } from './PhoenixEntityElement.js';
|
|
2
3
|
export { PhoenixLayoutElement, PhoenixLayoutElements, PhoenixLayoutElementWithChildren, usePhoenixLayoutElementExists, type PhoenixLayoutElementProps, type PhoenixLayoutElementsProps, type PhoenixLayoutElementWithChildrenProps, } from './PhoenixLayoutElement.js';
|
|
@@ -22,10 +22,15 @@ export declare class LoadingStateManager {
|
|
|
22
22
|
private readonly _phases$;
|
|
23
23
|
private readonly _hasError$;
|
|
24
24
|
private readonly _expectedPhases$;
|
|
25
|
+
private readonly _expectationCounts;
|
|
25
26
|
readonly startPhase: (phase: LoadingPhase) => void;
|
|
26
27
|
readonly endPhase: (id: string) => void;
|
|
27
28
|
readonly reportError: () => void;
|
|
28
29
|
readonly clearError: () => void;
|
|
30
|
+
/**
|
|
31
|
+
* Marks a phase as expected before it starts. Expectations are counted: the phase stays expected
|
|
32
|
+
* until every `expectPhase` call for it has been matched by a `dropExpectation`.
|
|
33
|
+
*/
|
|
29
34
|
readonly expectPhase: (id: string) => void;
|
|
30
35
|
readonly dropExpectation: (id: string) => void;
|
|
31
36
|
readonly state$: Observable<LoadingState>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var e;import{BehaviorSubject as s,combineLatest as
|
|
1
|
+
var e;import{BehaviorSubject as s,combineLatest as t,distinctUntilChanged as a,map as h}from"rxjs";export var BuiltInLoadingPhase=((e={}).SessionAuth="session-auth",e.PublishedData="published-data",e.FrontendData="frontend-data",e.BackendData="backend-data",e.DataFetch="data-fetch",e.SceneAssets="scene-assets",e);export class LoadingStateManager{_phases$=new s(new Map);_hasError$=new s(!1);_expectedPhases$=new s(new Set);_expectationCounts=new Map;startPhase=e=>{let s=new Map(this._phases$.value);s.set(e.id,e),this._phases$.next(s)};endPhase=e=>{let s=this._phases$.value;if(!s.has(e))return;let t=new Map(s);t.delete(e),this._phases$.next(t)};reportError=()=>{this._hasError$.next(!0)};clearError=()=>{this._hasError$.next(!1)};expectPhase=e=>{let s=this._expectationCounts.get(e)??0;if(this._expectationCounts.set(e,s+1),s>0)return;let t=new Set(this._expectedPhases$.value);t.add(e),this._expectedPhases$.next(t)};dropExpectation=e=>{let s=this._expectationCounts.get(e);if(void 0===s)return;if(s>1)return void this._expectationCounts.set(e,s-1);this._expectationCounts.delete(e);let t=new Set(this._expectedPhases$.value);t.delete(e),this._expectedPhases$.next(t)};state$=t([this._phases$,this._hasError$,this._expectedPhases$]).pipe(h(([e,s,t])=>{let a;return{isLoading:(a=Array.from(e.values())).length>0,hasError:s,phases:a,expectedPhaseIds:t}}),a((e,s)=>e.isLoading===s.isLoading&&e.hasError===s.hasError&&e.expectedPhaseIds===s.expectedPhaseIds&&e.phases.length===s.phases.length&&e.phases.every((e,t)=>e.id===s.phases[t]?.id&&e.progress===s.phases[t]?.progress&&e.label===s.phases[t]?.label)))}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salsita-ai/sdk",
|
|
3
|
-
"version": "0.50.0-develop.
|
|
3
|
+
"version": "0.50.0-develop.8837f064f9",
|
|
4
4
|
"description": "TypeScript SDK and CLI for building, running, and deploying Phoenix configurator applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"salsita",
|
|
@@ -298,8 +298,8 @@
|
|
|
298
298
|
"yaml": "2.9.0",
|
|
299
299
|
"yargs": "17.7.2",
|
|
300
300
|
"zod": "3.25.76",
|
|
301
|
-
"@salsita-ai/
|
|
302
|
-
"@salsita-ai/
|
|
301
|
+
"@salsita-ai/common": "0.50.0-develop.8837f064f9",
|
|
302
|
+
"@salsita-ai/contracts": "0.50.0-develop.8837f064f9"
|
|
303
303
|
},
|
|
304
304
|
"devDependencies": {
|
|
305
305
|
"@clerk/clerk-react": "5.61.8",
|