@salsita-ai/sdk 0.50.0-develop.d815756961 → 0.50.0-develop.d87b28256e
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/ConfiguratorApp.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/camera-control-system/camera-control-system.d.ts +1 -1
- package/dist/configurator/common/configurator-interface.d.ts +28 -1
- 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-context/ConfiguratorResponsiveProvider.d.ts +12 -0
- package/dist/configurator/configurator-context/ConfiguratorResponsiveProvider.js +1 -0
- package/dist/configurator/configurator-context/PreviewContextProvider.js +1 -1
- package/dist/configurator/configurator-context/ViewportStateSync.d.ts +2 -2
- package/dist/configurator/configurator-context/ViewportStateSync.js +1 -1
- package/dist/configurator/configurator-context/configurator-hooks/layout-hooks.d.ts +6 -0
- 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/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 +3 -2
- 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/mobile-tabs/use-mobile-tabs.d.ts +1 -1
- package/dist/configurator/ui/configurator-layout/slots/mobile-tabs/use-mobile-tabs.js +1 -1
- package/dist/configurator/ui/configurator-layout/slots/sidebar-tabs/use-sidebar-tabs.d.ts +1 -1
- package/dist/configurator/ui/configurator-layout/slots/sidebar-tabs/use-sidebar-tabs.js +1 -1
- package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.d.ts +1 -1
- package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.js +1 -1
- package/dist/configurator/ui/configurator-layout/universal/use-scene-safe-area.d.ts +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/AppLoader.js +3 -3
- package/dist/configurator/ui/phoenix-layout-elements/header/common/horizontal-tabs/DesktopHorizontalTabs.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/header/common/step-navigation/DesktopNavigationSteps.d.ts +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/header/common/step-navigation/DesktopNavigationSteps.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/header/common/step-navigation/StepsNavigation.d.ts +2 -8
- package/dist/configurator/ui/phoenix-layout-elements/header/common/step-navigation/StepsNavigation.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/FinalizeActions.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/NextStepButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/PreviousStepButton.js +1 -1
- package/dist/docs/index.json +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 +110 -58
- package/dist/docs/phoenix/finalization.mdx +1 -1
- package/dist/docs/phoenix/layout-slots.mdx +11 -11
- package/dist/docs/phoenix/testing.mdx +1 -1
- package/dist/phoenix/services/swc.js +1 -1
- package/dist/react-sdk/domain-context/index.d.ts +2 -0
- package/dist/react-sdk/domain-context/index.js +1 -1
- package/dist/react-sdk/domain-context/system-hooks/step-navigation-hooks.d.ts +57 -0
- package/dist/react-sdk/domain-context/system-hooks/step-navigation-hooks.js +1 -0
- package/dist/react-sdk/domain-context/system-hooks/tab-navigation-hooks.d.ts +61 -0
- package/dist/react-sdk/domain-context/system-hooks/tab-navigation-hooks.js +1 -0
- 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/handlers/FinalizeActionRunner.d.ts +13 -0
- package/dist/react-sdk/handlers/FinalizeActionRunner.js +1 -0
- package/dist/react-sdk/handlers/FinalizeActionsHost.d.ts +10 -0
- package/dist/react-sdk/handlers/FinalizeActionsHost.js +1 -0
- package/dist/react-sdk/handlers/FinalizeActionsProvider.d.ts +11 -0
- package/dist/react-sdk/handlers/FinalizeActionsProvider.js +1 -0
- package/dist/react-sdk/handlers/finalize-actions-context.d.ts +6 -0
- package/dist/react-sdk/handlers/finalize-actions-context.js +1 -0
- package/dist/react-sdk/handlers/finalize-actions-store.d.ts +23 -0
- package/dist/react-sdk/handlers/finalize-actions-store.js +1 -0
- package/dist/react-sdk/handlers/index.d.ts +5 -0
- package/dist/react-sdk/handlers/index.js +1 -1
- package/dist/react-sdk/handlers/use-finalize-action.d.ts +3 -2
- package/dist/react-sdk/handlers/use-finalize-action.js +1 -1
- package/dist/react-sdk/handlers/use-finalize-actions.d.ts +53 -0
- package/dist/react-sdk/handlers/use-finalize-actions.js +1 -0
- package/dist/react-sdk/index.d.ts +1 -0
- package/dist/react-sdk/index.js +1 -1
- package/dist/react-sdk/responsive-state/ResponsiveStateProvider.d.ts +25 -0
- package/dist/react-sdk/responsive-state/ResponsiveStateProvider.js +1 -0
- package/dist/react-sdk/responsive-state/canvas-padding.d.ts +14 -0
- package/dist/react-sdk/responsive-state/canvas-padding.js +1 -0
- package/dist/react-sdk/responsive-state/index.d.ts +5 -0
- package/dist/react-sdk/responsive-state/index.js +1 -0
- package/dist/react-sdk/responsive-state/responsive-state-manager.d.ts +111 -0
- package/dist/react-sdk/responsive-state/responsive-state-manager.js +1 -0
- package/dist/react-sdk/responsive-state/use-responsive-state-manager.d.ts +10 -0
- package/dist/react-sdk/responsive-state/use-responsive-state-manager.js +1 -0
- package/dist/react-sdk/responsive-state/use-responsive-state.d.ts +11 -0
- package/dist/react-sdk/responsive-state/use-responsive-state.js +1 -0
- package/dist/ui-sdk/responsive-application/ResponsiveApplicationProvider.d.ts +9 -0
- package/dist/ui-sdk/responsive-application/ResponsiveApplicationProvider.js +1 -1
- package/dist/ui-sdk/responsive-application/index.d.ts +1 -0
- package/dist/ui-sdk/responsive-application/responsive-state-store.d.ts +57 -0
- package/dist/ui-sdk/responsive-application/responsive-state-store.js +1 -0
- package/dist/ui-sdk/responsive-application/use-store-responsive-info.d.ts +7 -0
- package/dist/ui-sdk/responsive-application/use-store-responsive-info.js +1 -0
- package/package.json +7 -6
package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/FinalizeActions.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as e,jsxs as
|
|
1
|
+
import{jsx as e,jsxs as n,Fragment as r}from"react/jsx-runtime";import{Trans as t}from"@lingui/react";import{Button as i,ButtonGroup as a,CircularProgress as o,ListItemIcon as l,ListItemText as s,Menu as c,MenuItem as m,buttonClasses as p,styled as d,svgIconClasses as g}from"@mui/material";import{useSnackbar as u}from"notistack";import{useCallback as h,useContext as f,useRef as y,useState as k}from"react";import{FinalizeActionsContext as z,useFinalizeActions as b,useLocalizable as x}from"#package/react-sdk";import{PhxIcon as C,ResponsiveSidebarFooterButton as P,TooltipWithArrow as v,useResponsive as S}from"#package/ui-sdk";let w=d(a)({display:"grid",gridTemplateColumns:"minmax(0, 1fr) min-content "}),j=d(i)(({theme:{spacing:e}})=>({[`&.${p.containedSizeSmall}`]:{minInlineSize:0,[`.${g.root}`]:{fontSize:18}},[`&.${p.containedSizeLarge}`]:{paddingInline:e(1)}})),D=n=>{let{enqueueSnackbar:r}=u(),i=f(z),{key:a,disabled:o,isPending:l,run:s}=n,c=y(!1);return h(async()=>{if(o||l||c.current)return!1;c.current=!0;try{await s()}finally{c.current=!1}let n=i?.getSnapshot().get(a);return n?.status!=="ready"||!n.action.error||(r({message:e(t,{id:"sdk.messages.finalize.error",message:"Oops, something went wrong"}),variant:"error"}),!1)},[o,r,l,a,s,i])},I=({action:n,hints:r})=>{let t=D(n),i=h(e=>{e.preventDefault(),e.stopPropagation(),t()},[t]);return e(P,{tooltip:r??n.title,busy:n.isPending,disabled:n.disabled,onClick:i,slotProps:{button:{startIcon:e(C,{name:n.icon})}},children:n.label})},O=({action:r,hints:t,onClose:i})=>{let a=D(r),c=h(async e=>{e.preventDefault(),e.stopPropagation(),await a()&&i()},[a,i]);return e(v,{title:t,placement:"top",children:e("span",{children:n(m,{disabled:r.disabled,onClick:c,children:[e(l,{children:r.isPending?e(o,{size:20}):e(C,{name:r.icon})}),e(s,{primary:r.label,secondary:r.title})]})})})};export const FinalizeActions=()=>{let t=x({id:"sdk.layout.finalize.moreActions",message:"More finalization options"}),{actions:i,primary:a,secondary:o,hints:l}=b(),[s,m]=k(null),p=!!s,{isDesktop:d}=S(),g={color:"primary",variant:"contained",size:d?"large":"small"},u=h(e=>{m(e.currentTarget)},[]),f=h(()=>{m(null)},[]);return a?1===i.length?e(I,{action:a,hints:l},a.key):n(r,{children:[n(w,{...g,children:[e(I,{action:a,hints:l},a.key),e(j,{...g,"aria-label":t,"aria-haspopup":"menu","aria-expanded":p,onClick:u,children:e(C,{name:"CaretDown"})})]}),e(c,{anchorEl:s,anchorOrigin:{vertical:"top",horizontal:"right"},transformOrigin:{vertical:"bottom",horizontal:"right"},MenuListProps:{dense:!0},slotProps:{paper:{sx:{mt:-2}}},open:p,onClose:f,children:o.map(n=>e(O,{action:n,hints:l,onClose:f},n.key))})]}):null};
|
package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/NextStepButton.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as
|
|
1
|
+
import{jsx as e}from"react/jsx-runtime";import{Trans as t}from"@lingui/react";import{Button as r}from"@mui/material";import{memo as a,useCallback as o}from"react";import{createPhoenixLayoutElement as i,nextStepButtonSchema as m}from"#package/domain";import{FrontendAnalyticsFactory as n,useAnalyticsService as s,useStepNavigation as p}from"#package/react-sdk";import{useResponsive as c}from"#package/ui-sdk";export const NextStepButton=i({schema:m,component:a(()=>{let a=s(),{canGoNext:i,next:m}=p(),{isDesktop:l}=c(),d=o(()=>{i?m():a.push(n.NextStepButtonClicked())},[a,i,m]);return l?e(r,{variant:"contained",color:"primary",size:"large",onClick:d,children:e(t,{id:"sdk.layout.sidebar.nextStep.label",message:"Next step"})}):e(r,{variant:"contained",color:"primary",size:"small",onClick:d,children:e(t,{id:"sdk.layout.sidebar.nextStepShort.label",message:"Next"})})})});
|
package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/PreviousStepButton.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as
|
|
1
|
+
import{jsx as e}from"react/jsx-runtime";import{IconButton as t,Tooltip as r}from"@mui/material";import{memo as o,useCallback as i}from"react";import{createPhoenixLayoutElement as a,previousStepButtonSchema as m}from"#package/domain";import{FrontendAnalyticsFactory as s,useAnalyticsService as n,useLocalizable as l,useStepNavigation as p}from"#package/react-sdk";import{PhxIcon as c,useResponsive as d}from"#package/ui-sdk";import{useBackToPresetEnabled as u,useConfiguratorRouting as f,useSteps as k}from"../../../../configurator-context/index.js";let g={id:"sdk.layout.sidebar.previousStep.label",message:"Previous step"};export const PreviousStepButton=a({schema:m,component:o(o=>{let{icon:a}=o,m=l(g),x=n(),h=u(),v=f(),{canGoPrevious:S,previous:b}=p(),{isDesktop:j}=d(),{previousStepDisabled:C}=k();return e(r,{title:m,placement:"top",children:e("span",{style:{alignSelf:"center"},children:e(t,{variant:"muted",color:"default",size:j?"medium":"small",disabled:C,onClick:i(()=>{S?b():(x.push(s.PreviousStepButtonClicked()),h&&v.goToIndex())},[x,h,S,b,v]),children:e(c,{name:a??"CaretLeft"})})})})})});
|
package/dist/docs/index.json
CHANGED
|
@@ -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,29 +401,46 @@ 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
|
+
|
|
427
444
|
### Loaded Configuration UI
|
|
428
445
|
|
|
429
446
|
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.
|
|
@@ -460,10 +477,10 @@ export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout
|
|
|
460
477
|
- `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
478
|
- 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`.
|
|
462
479
|
- 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.useFinalizeAction` reports its error as
|
|
480
|
+
- 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
481
|
- Any project layout element named `ConfiguratorLayout` takes over the page.
|
|
465
482
|
|
|
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,
|
|
483
|
+
`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, and the built-in error page as a `ConfiguratorErrorPage` 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
484
|
|
|
468
485
|
#### Hooks for a Project Layout
|
|
469
486
|
|
|
@@ -494,18 +511,52 @@ Texts from the spec, such as option titles and the `detail` fields, are `Localiz
|
|
|
494
511
|
|
|
495
512
|
**Mode, fullscreen, preview and navigation**
|
|
496
513
|
|
|
497
|
-
| Hook | Returns
|
|
498
|
-
| ---------------------------------------------------------------------------- |
|
|
499
|
-
| `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
|
-
| `phx.frontend.useIsFullscreenActive()` and `phx.frontend.useSetFullscreen()` | The fullscreen flag, a `boolean`, and a function `(enabled: boolean) => void` that sets it.
|
|
501
|
-
| `usePreview()` from `@salsita-ai/sdk/configurator` | `{ isPreview, isExpanded, isPreviewEnabled, isHidden, enter, leave }`. The layout shows a preview start screen while `isPreview` is `true`.
|
|
502
|
-
| `phx.frontend.useActiveNavigationState()` | The active navigation state, one of the spec's `navigationStates`.
|
|
503
|
-
| `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
|
-
| `phx.frontend.useActiveViewState()` and `phx.frontend.useActiveToolState()` | The active view state and tool state.
|
|
505
|
-
| `phx.frontend.useSetViewState()` | `(state) => void`, which makes the given view state, one of the spec's `viewStates`, the active one; setting the active state again keeps it. Cameras and components filtered by `viewStates` follow the change.
|
|
506
|
-
| `phx.frontend.useResetCamera()` | `() => void`, which moves the camera back to the framing of the active camera configuration, undoing the user's orbit, pan and zoom, as the toolbar's reset button does. Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it, not `@salsita-ai/sdk/react`.
|
|
507
|
-
| `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
|
-
| `
|
|
514
|
+
| Hook | Returns |
|
|
515
|
+
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
516
|
+
| `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. |
|
|
517
|
+
| `phx.frontend.useIsFullscreenActive()` and `phx.frontend.useSetFullscreen()` | The fullscreen flag, a `boolean`, and a function `(enabled: boolean) => void` that sets it. |
|
|
518
|
+
| `usePreview()` from `@salsita-ai/sdk/configurator` | `{ isPreview, isExpanded, isPreviewEnabled, isHidden, enter, leave }`. The layout shows a preview start screen while `isPreview` is `true`. |
|
|
519
|
+
| `phx.frontend.useActiveNavigationState()` | The active navigation state, one of the spec's `navigationStates`. |
|
|
520
|
+
| `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. |
|
|
521
|
+
| `phx.frontend.useActiveViewState()` and `phx.frontend.useActiveToolState()` | The active view state and tool state. |
|
|
522
|
+
| `phx.frontend.useSetViewState()` | `(state) => void`, which makes the given view state, one of the spec's `viewStates`, the active one; setting the active state again keeps it. Cameras and components filtered by `viewStates` follow the change. |
|
|
523
|
+
| `phx.frontend.useResetCamera()` | `() => void`, which moves the camera back to the framing of the active camera configuration, undoing the user's orbit, pan and zoom, as the toolbar's reset button does. Besides `phx.frontend`, `@salsita-ai/sdk/configurator` exports it, not `@salsita-ai/sdk/react`. |
|
|
524
|
+
| `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. |
|
|
525
|
+
| `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. |
|
|
526
|
+
| `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. |
|
|
527
|
+
| `useActiveViewports()` from `@salsita-ai/sdk/react` | 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. |
|
|
528
|
+
|
|
529
|
+
**Steps and tabs**
|
|
530
|
+
|
|
531
|
+
A project layout builds its steps and tabs from the spec, so the structure stays in `navigationSteps`, `sidebarTabs` and `horizontalTabs`, where `$filter` switches it, and the layout decides how it looks. `phx.frontend.useStepNavigation()` gives a wizard its steps with a `status` each, and `next`, `previous` and `goTo`; `phx.frontend.useTabNavigation()` gives tabs with `isActive` each, and `select`. Both enter a navigation state, so controls, cameras and components filtered by `navigationStates` follow. Neither hides anything in read mode or blocks a step on violations: a layout that should not move on while the configuration is invalid combines `canGoNext` with `useStateValidity()`, and a stepper that offers only earlier steps enables the steps whose `status` is `'complete'`.
|
|
532
|
+
|
|
533
|
+
```tsx
|
|
534
|
+
export const Wizard = () => {
|
|
535
|
+
const { steps, activeStep, canGoNext, canGoPrevious, next, previous, goTo } = phx.frontend.useStepNavigation()
|
|
536
|
+
|
|
537
|
+
return (
|
|
538
|
+
<nav aria-label="Steps">
|
|
539
|
+
<ol>
|
|
540
|
+
{steps.map(step => (
|
|
541
|
+
<li key={step.state} aria-current={step.status === 'active' ? 'step' : undefined}>
|
|
542
|
+
<button type="button" disabled={step.status === 'upcoming'} onClick={() => goTo(step.index)}>
|
|
543
|
+
{step.index + 1}. {step.label}
|
|
544
|
+
</button>
|
|
545
|
+
</li>
|
|
546
|
+
))}
|
|
547
|
+
</ol>
|
|
548
|
+
<button type="button" disabled={!canGoPrevious} onClick={previous}>
|
|
549
|
+
Back
|
|
550
|
+
</button>
|
|
551
|
+
{canGoNext && (
|
|
552
|
+
<button type="button" onClick={next}>
|
|
553
|
+
Next: {steps[(activeStep?.index ?? -1) + 1]?.label}
|
|
554
|
+
</button>
|
|
555
|
+
)}
|
|
556
|
+
</nav>
|
|
557
|
+
)
|
|
558
|
+
}
|
|
559
|
+
```
|
|
509
560
|
|
|
510
561
|
**Violations**
|
|
511
562
|
|
|
@@ -514,59 +565,60 @@ Texts from the spec, such as option titles and the `detail` fields, are `Localiz
|
|
|
514
565
|
| `phx.frontend.useStateValidity()` | The validity of the last committed state: `{ valid: true, severity: null }`, or `{ valid: true, severity: 'notice' \| 'warning', entityViolations }`. A change that leaves a `fixable` or `fatal` violation is rejected and never committed, and neither is a change during which an error was thrown, so neither is seen here. |
|
|
515
566
|
| `phx.frontend.useFinalizeViolations()` | `{ disabled, hints, violations }`. `disabled` is `true` while a `warning`, `fixable` or `fatal` violation holds or the configuration failed to save; `hints` is the violations' text, or `undefined`. |
|
|
516
567
|
| `phx.frontend.useConfiguratorMessages(onMessage)` | Nothing. It calls `onMessage(message)` once per message: `{ kind: 'violation', entityId, violation, text }` for each violation of the rejected state, including notices and warnings already present, so `violation.severity` tells the reason for the rejection (`fixable` or `fatal`) from them, `{ kind: 'sideEffect', text }` for each hint of a committed update, `{ kind: 'error', error, text }` when an error is thrown while a change is validated, with `text` the generic "Oops, something went wrong", `{ kind: 'saveError', error, text }` each time saving the configuration fails, with `error` what the save rejected with and `text` a generic message, and `{ kind: 'saveRecovery', text }` when a save succeeds after a failed one, with `text` "Configuration updated". `text` is resolved to the current language; a violation's is its `message`, or its `type` without one, and the built-in snackbar prefixes it with `entityId`. Hints committed before the layout mounted are not delivered, nor is a save that failed before it mounted; when the most recent change was rejected or threw, its messages are delivered again when the hook mounts. `onMessage` may be a new function on every render. On `phx.frontend` the message is typed by the project, so `violation.type` narrows to its violation types; from `@salsita-ai/sdk/react` the type is `ConfiguratorMessage`. |
|
|
517
|
-
| `phx.frontend.useFinalizeAction(handler)` | `{ run, isPending, error, disabled, hints }` for the `finalizeAction` whose `handler` it is given; see the Finalize paragraph below for how to call it. `run()` records the finalize-button click for analytics and runs the project's handler,
|
|
568
|
+
| `phx.frontend.useFinalizeAction(handler)` | `{ run, isPending, error, disabled, hints }` for the `finalizeAction` whose `handler` it is given; see the Finalize paragraph below for how to call it. `run()` records the finalize-button click for analytics and runs the project's handler, does nothing while `disabled` or `isPending`, and returns a promise that resolves once the handler has settled and never rejects; `error` is what the handler's last run rejected with, or `null`; `disabled` and `hints` are those of `useFinalizeViolations()`. The type of the result is `UseFinalizeActionResult` from `@salsita-ai/sdk/configurator`, which also exports the hook. |
|
|
569
|
+
| `phx.frontend.useFinalizeActions()` | `{ actions, primary, secondary, disabled, hints }`: every active `finalizeAction` of the spec in spec order, `primary` the first `'primary'` one, or else the first, or `null`, and `secondary` the others; see the Finalize paragraph below. Each action is `{ key, handler, variant, label, title, icon, run, isPending, error, disabled }`, with `label` and `title` resolved; `run()` records the finalize-button click for analytics and runs the action's handler, does nothing while `disabled` or `isPending`, and returns a promise that never rejects. `disabled` and `hints` are those of `useFinalizeViolations()`. Besides `phx.frontend`, `@salsita-ai/sdk/react` exports it. |
|
|
518
570
|
|
|
519
571
|
`LayoutSlots.Services` shows every message of `useConfiguratorMessages` as a snackbar, `saveError` and `saveRecovery` included; see the messages paragraph of [Loaded Configuration UI](#loaded-configuration-ui) for choosing between the two and for the messages that stay snackbars.
|
|
520
572
|
|
|
521
573
|
**Finalize**
|
|
522
574
|
|
|
523
|
-
A project layout finalizes over the spec's `finalizeAction` components
|
|
524
|
-
|
|
525
|
-
Each action gets a component of its own that calls `phx.frontend.useFinalizeAction(action.state.handler)` once, never in a loop or conditionally, because the hook runs the project's handler factory, which calls hooks. The layout wraps that component in `<PhoenixHandlerBoundary name={action.state.handler}>` from `@salsita-ai/sdk/react`, so an edited handler takes effect without a reload. The hook returns:
|
|
575
|
+
A project layout finalizes over the spec's `finalizeAction` components, each `{ variant, label, title, icon, handler }`, where `variant` is `'primary'` or `'secondary'` and `handler` names the project's handler registered with `phx.frontend.createPhoenixHandler`. `phx.frontend.useFinalizeActions()` is the convenient way: it lists them with what a button needs, runs their handlers itself, and can be called anywhere in the layout, including a dialog the layout opens with `useModal`, as often as it likes. It returns:
|
|
526
576
|
|
|
527
|
-
- `
|
|
528
|
-
- `isPending`, `true` while the handler runs.
|
|
529
|
-
- `error`, what the handler's last run rejected with, or `null`; a rejection that is not an `Error` is wrapped in one, with the value as its `cause`. It is cleared when the action runs again. No snackbar is raised; the layout shows the error its own way.
|
|
577
|
+
- `actions`, every active `finalizeAction` in spec order, and `primary` and `secondary`, the same actions split as the built-in footer splits them: `primary` is the first `'primary'` action, or else the first action, or `null` without any, and `secondary` the others in spec order.
|
|
530
578
|
- `disabled` and `hints`, those of `useFinalizeViolations()`: finalizing is not possible while a `warning`, `fixable` or `fatal` violation holds or the configuration failed to save, and `hints` says why.
|
|
531
579
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
```tsx
|
|
535
|
-
import { finalizeActionComponent } from '@salsita-ai/sdk/domain'
|
|
536
|
-
import { PhoenixHandlerBoundary, useResolveLocalizable } from '@salsita-ai/sdk/react'
|
|
580
|
+
Each action has `key`, a stable key for React, `handler`, `variant`, `label` and `title` resolved to the current language, `icon`, a `PhxIcon` name, and:
|
|
537
581
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
<div>
|
|
543
|
-
<button type="button" disabled={disabled || isPending} onClick={run} title={hints}>
|
|
544
|
-
{isPending ? 'Sending…' : label}
|
|
545
|
-
</button>
|
|
546
|
-
{error && <p role="alert">{error.message}</p>}
|
|
547
|
-
</div>
|
|
548
|
-
)
|
|
549
|
-
}
|
|
582
|
+
- `run()`, which records the finalize-button click for analytics and then runs the handler. It does nothing while `disabled` or `isPending`. It returns a promise that resolves once the handler has settled and never rejects, so a menu or a sheet can close after it.
|
|
583
|
+
- `isPending`, `true` while the handler runs.
|
|
584
|
+
- `error`, what the handler's last run rejected with, or `null`; a rejection that is not an `Error` is wrapped in one, with the value as its `cause`. It is cleared when the action runs again. No snackbar is raised; the layout shows the error its own way. A handler that throws while it is loaded, for example because it calls a hook that needs a provider the configurator does not have, makes `error` that error and the action `disabled`.
|
|
585
|
+
- `disabled`, `true` while finalizing is not possible, or when the handler could not be loaded.
|
|
550
586
|
|
|
587
|
+
```tsx
|
|
551
588
|
export const Finalize = () => {
|
|
552
589
|
const { accessInfo } = phx.frontend.useAccessService()
|
|
553
|
-
const
|
|
554
|
-
const actions = phx.frontend.useComponents({ componentTypes: [finalizeActionComponent] }) ?? []
|
|
555
|
-
const primary = actions.find(action => action.state.variant === 'primary') ?? actions[0]
|
|
590
|
+
const { primary, secondary, hints } = phx.frontend.useFinalizeActions()
|
|
556
591
|
|
|
557
592
|
if (accessInfo.viewMode || !primary) {
|
|
558
593
|
return null
|
|
559
594
|
}
|
|
560
595
|
|
|
561
596
|
return (
|
|
562
|
-
<
|
|
563
|
-
<
|
|
564
|
-
|
|
597
|
+
<div>
|
|
598
|
+
<button
|
|
599
|
+
type="button"
|
|
600
|
+
disabled={primary.disabled || primary.isPending}
|
|
601
|
+
onClick={() => void primary.run()}
|
|
602
|
+
title={hints}
|
|
603
|
+
>
|
|
604
|
+
{primary.isPending ? 'Sending…' : primary.label}
|
|
605
|
+
</button>
|
|
606
|
+
{primary.error && <p role="alert">{primary.error.message}</p>}
|
|
607
|
+
{secondary.map(action => (
|
|
608
|
+
<button key={action.key} type="button" disabled={action.disabled} onClick={() => void action.run()}>
|
|
609
|
+
{action.label}
|
|
610
|
+
</button>
|
|
611
|
+
))}
|
|
612
|
+
</div>
|
|
565
613
|
)
|
|
566
614
|
}
|
|
567
615
|
```
|
|
568
616
|
|
|
569
|
-
The
|
|
617
|
+
The configurator runs the handlers outside the layout, beside it, whenever a configuration is open, also in read mode and on every step. A handler can use what the configurator provides, such as `useModal`, but not a provider the layout itself adds; such a handler fails to load and shows as that action's `error`. An action's `isPending` and `error` are kept while the layout re-renders and while its components unmount and mount again.
|
|
618
|
+
|
|
619
|
+
To run a single action the layout already knows, `phx.frontend.useFinalizeAction(handler)` runs it in the layout itself and returns `{ run, isPending, error, disabled, hints }`, with the same meaning. It runs the project's handler factory, which calls hooks, so the layout calls it once per component, never in a loop or conditionally, and wraps that component in `<PhoenixHandlerBoundary name={handler}>` from `@salsita-ai/sdk/react`, so an edited handler takes effect without a reload. Its `isPending` and `error` belong to the mounted component: when it unmounts during a run, the handler keeps running, and a component mounted afterwards starts with `isPending` false and no `error`. [Custom Layout](?path=/docs/layouts-custom-layout--docs#request-a-quote) runs a finalize action this way from its review step.
|
|
620
|
+
|
|
621
|
+
Neither hook checks read mode, so the layout hides finalizing while `accessInfo.viewMode` is `true`, as above.
|
|
570
622
|
|
|
571
623
|
Instead of its own buttons, a project layout can mount `<FinalizeActions />` from `@salsita-ai/sdk/configurator`: the built-in footer's finalize buttons, the primary action plus a menu of the others, disabled with the hints of `useFinalizeViolations()`, with a spinner while the handler runs and a snackbar when it fails. It renders nothing without a `finalizeAction`, and it does not check read mode either.
|
|
572
624
|
|
|
@@ -15,7 +15,7 @@ Finalization is Phoenix's system for completing configurator sessions and proces
|
|
|
15
15
|
|
|
16
16
|
> For integration-specific finalization flows — how host-specific data is passed in and how the host is notified on completion (event dispatch in `headless`, redirect in `handoff`) — see [Finalization (Internal)](?path=/docs/phoenix-integration-finalization-internal--docs) and [Finalization (External)](?path=/docs/phoenix-integration-finalization-external--docs).
|
|
17
17
|
|
|
18
|
-
The universal sidebar footer offers the spec's `finalizeAction` components: Phoenix selects one primary footer action and places additional finalization actions in its menu. It never opens a `finalizePage`; the `finalizePage` and `finalizeButton` components are legacy, declared only in TypeScript, and used by the basic sidebar footer and the legacy layouts. Built-in finalization actions use configuration validity to disable the footer action and provide pending and generic error states, including for add-to-cart handlers. See [Universal Sidebar Footer](?path=/docs/layouts-sidebar-footer-universal--docs) for the layout behavior. A project layout (`ConfiguratorLayout`) offers the same actions with its own buttons through `phx.frontend.
|
|
18
|
+
The universal sidebar footer offers the spec's `finalizeAction` components: Phoenix selects one primary footer action and places additional finalization actions in its menu. It never opens a `finalizePage`; the `finalizePage` and `finalizeButton` components are legacy, declared only in TypeScript, and used by the basic sidebar footer and the legacy layouts. Built-in finalization actions use configuration validity to disable the footer action and provide pending and generic error states, including for add-to-cart handlers. See [Universal Sidebar Footer](?path=/docs/layouts-sidebar-footer-universal--docs) for the layout behavior. A project layout (`ConfiguratorLayout`) offers the same actions with its own buttons through `phx.frontend.useFinalizeActions`, which lists them with the primary one picked as the footer picks it, each with its own `run`, pending state, error and disabled state, or through `phx.frontend.useFinalizeAction`, which runs one known action's handler and returns the same state with hints; see the Finalize paragraph of [Configurator Interface → Hooks for a Project Layout](?path=/docs/phoenix-configurator-interface--docs#hooks-for-a-project-layout).
|
|
19
19
|
|
|
20
20
|
## How Finalization Works
|
|
21
21
|
|
|
@@ -42,20 +42,20 @@ export const Toolbar = () => (
|
|
|
42
42
|
|
|
43
43
|
- Mount `SharedCanvas`, not `Canvas`: `Canvas` suspends while it probes the GPU on its first mount, and `SharedCanvas` holds the boundary that catches it. Without either, the app loader stays on screen unless the project sets `appLoader: legacy`.
|
|
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
|
-
- 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.useFinalizeAction` raises no snackbar; its error is the
|
|
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
|
|
|
47
47
|
## Desktop sidebar
|
|
48
48
|
|
|
49
|
-
| Member | Shows
|
|
50
|
-
| ----------------------------- |
|
|
51
|
-
| `DesktopSidebar` | The whole built-in desktop sidebar: tabs, back button, property pages and sidebar footer with their spacing, scrolling and empty states, and the assistant panel. Props: `width`, and `side` (`'start'` or `'end'`, typed `DesktopSidebarSide`; `DesktopSidebarProps` types both)
|
|
52
|
-
| `DesktopSidebarTabs` | Tabs that switch the navigation state
|
|
53
|
-
| `useSidebarTabs()` | A hook, `{ tabs, activeTab, onChange }`: each tab is `{ label, icon, value, isActive }`, `activeTab` is the active tab's value or `'none'`, and `onChange(value)` switches to that tab's first navigation state
|
|
54
|
-
| `DesktopSidebarBackButton` | A "Back" button. Props: the MUI `Button` props and `icon`, also settable in the theme as `PhxDesktopSidebarBackButton`; the application config's `sidebarBackIcon` takes precedence over both
|
|
55
|
-
| `DesktopSidebarContent` | The property pages of the selected entity with its entity header, a stacked page opened from them, or the summary page while the summary is shown. With the application config's `ignoreSelectionInSidebar` set, every `propertyPage` of every entity shows under the root, whatever is selected
|
|
56
|
-
| `useSidebarContent(options?)` | A hook, `{ content, goBack, hasMiniSidebar, hasSplitSidebar }`: `content` is what `DesktopSidebarContent` shows, `goBack` is the back action or `null`. `options` sets the `HeaderContainer` and `BodyContainer` components the content is wrapped in
|
|
57
|
-
| `EntityHeader` | An entity's title, subtitle, description and thumbnail, with its primary and secondary buttons, such as delete or replace. Props, typed `EntityHeaderProps`: `entityId` and `headerComponent`
|
|
58
|
-
| `DesktopSidebarFooter` | The price and the actions
|
|
49
|
+
| Member | Shows | Fed by | With nothing configured | Read mode |
|
|
50
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `DesktopSidebar` | The whole built-in desktop sidebar: tabs, back button, property pages and sidebar footer with their spacing, scrolling and empty states, and the assistant panel. Props: `width`, and `side` (`'start'` or `'end'`, typed `DesktopSidebarSide`; `DesktopSidebarProps` types both) | The members below | As its parts | As its parts. In fullscreen it stays in place, transparent and inert |
|
|
52
|
+
| `DesktopSidebarTabs` | Tabs that switch the navigation state | The `sidebarTabs` components of the selected entity; the `horizontalTabs` component is not shown here | Nothing; also nothing while the summary is shown | No change |
|
|
53
|
+
| `useSidebarTabs()` | A hook, `{ tabs, activeTab, onChange }`: each tab is `{ label, icon, value, isActive }`, `activeTab` is the active tab's value or `'none'`, and `onChange(value)` switches to that tab's first navigation state. A project layout reads its tabs with `phx.frontend.useTabNavigation()` instead, see [Configurator Interface → Hooks for a Project Layout](?path=/docs/phoenix-configurator-interface--docs#hooks-for-a-project-layout) | As `DesktopSidebarTabs` | `tabs` is empty | No change |
|
|
54
|
+
| `DesktopSidebarBackButton` | A "Back" button. Props: the MUI `Button` props and `icon`, also settable in the theme as `PhxDesktopSidebarBackButton`; the application config's `sidebarBackIcon` takes precedence over both | The sidebar's way back: a navigation state to pop, a stacked property page, or a selected entity other than the root, unless the application config sets `showSelectionBackButton` to `false` or sets `ignoreSelectionInSidebar` | Nothing while there is no way back | No change |
|
|
55
|
+
| `DesktopSidebarContent` | The property pages of the selected entity with its entity header, a stacked page opened from them, or the summary page while the summary is shown. With the application config's `ignoreSelectionInSidebar` set, every `propertyPage` of every entity shows under the root, whatever is selected | `propertyPage`, `entityHeader`, `simpleSummary` and `customSummaryPage` components, and `ignoreSelectionInSidebar` | Nothing | **No change: the controls stay editable.** Limit them with `$filter.modes`, or render something else in read mode |
|
|
56
|
+
| `useSidebarContent(options?)` | A hook, `{ content, goBack, hasMiniSidebar, hasSplitSidebar }`: `content` is what `DesktopSidebarContent` shows, `goBack` is the back action or `null`. `options` sets the `HeaderContainer` and `BodyContainer` components the content is wrapped in | As `DesktopSidebarContent` | `content` is empty | No change |
|
|
57
|
+
| `EntityHeader` | An entity's title, subtitle, description and thumbnail, with its primary and secondary buttons, such as delete or replace. Props, typed `EntityHeaderProps`: `entityId` and `headerComponent` | The texts and thumbnail from the entity's `detail` component (the title falls back to the entity type); the buttons from the `entityHeader` component passed in | — | No change |
|
|
58
|
+
| `DesktopSidebarFooter` | The price and the actions | `basicSidebarFooter`, `universalSidebarFooter`, `readSidebarFooter`, `hiddenSidebarFooter` or `customSidebarFooter`. The basic footer offers a `finalizeButton`, else a button for a `finalizePage`, else a disabled button; the universal footer offers the `finalizeAction` and `otherAction` components | The basic footer: the price and a finalize button. With a `navigationSteps` component of two or more steps, previous and next step buttons and the finalize button only on the last step; with a summary and no steps, a **Summary** button until the summary is shown; in a preset embed, **Save Template** instead of finalize | With nothing configured, the read footer: price only. The basic footer hides its buttons. **The universal footer still shows its finalize and other actions to a viewer**; limit them with `$filter.modes` |
|
|
59
59
|
|
|
60
60
|
A sidebar footer with `universalSidebarFooter` does not check read mode, although the component's own description says its buttons are hidden then. A viewer of a shared configuration sees its actions. The `custom` footer renders its children unchanged as well.
|
|
61
61
|
|
|
@@ -34,7 +34,7 @@ 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
|
|
37
|
+
elements, the built-in layout, preset screen, not-found, not-authenticated and error pages, controls, toolbar
|
|
38
38
|
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
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{transform as e}from"@swc/core";import{remove as t}from"fs-extra";import{copyFile as s,mkdir as o,readFile as a,writeFile as r}from"fs/promises";import{globby as i}from"globby";import{createRequire as c}from"node:module";import{dirname as l,relative as n,resolve as p}from"path";import{Subject as m}from"rxjs";import{watch as w}from"#package/watch";import{createLogger as d}from"../utils/index.js";let u=()=>({sourceMaps:!0,module:{type:"es6"},jsc:{target:"es2022",parser:{syntax:"typescript",tsx:!0},transform:{react:{runtime:"automatic"}},keepClassNames:!0,experimental:{plugins:[[c(import.meta.url).resolve("@lingui/swc-plugin"),{}]]}}}),f=async(e,t,a)=>{let r=process.cwd(),c=p(r,e),m=p(r,t),w=await i(["**/*.{html,css,json,txt,md}","**/.env*"],{cwd:c,ignore:["**/*.{spec,test}.{html,css,json,txt,md}","**/__tests__/**","**/__mocks__/**","**/tsconfig.json",".env.generated"],absolute:!1}),d=0,u=0;for(let e of w)try{let t=p(c,e),a=p(m,e);await o(l(a),{recursive:!0}),await s(t,a),d++}catch(t){u++,console.error(`Failed to copy ${n(r,p(c,e))}:`,t)}if(u>0)throw Error(`Failed to copy ${u} file(s)`);d>0&&a.debug(`Copied ${d} non-compilable file(s)`)};export const compileWithSwc=async(s,c)=>{let{sourceDir:m="src",outDir:w="./node_modules/.phoenix"}=s??{},g=c?.logger??d(),h=process.cwd(),
|
|
1
|
+
import{transform as e}from"@swc/core";import{remove as t}from"fs-extra";import{copyFile as s,mkdir as o,readFile as a,writeFile as r}from"fs/promises";import{globby as i}from"globby";import{createRequire as c}from"node:module";import{dirname as l,relative as n,resolve as p}from"path";import{Subject as m}from"rxjs";import{watch as w}from"#package/watch";import{createLogger as d}from"../utils/index.js";let u=()=>({sourceMaps:!0,module:{type:"es6"},jsc:{target:"es2022",parser:{syntax:"typescript",tsx:!0},transform:{react:{runtime:"automatic"}},keepClassNames:!0,experimental:{plugins:[[c(import.meta.url).resolve("@lingui/swc-plugin"),{}]],cacheRoot:"node_modules/.cache/swc"}}}),f=async(e,t,a)=>{let r=process.cwd(),c=p(r,e),m=p(r,t),w=await i(["**/*.{html,css,json,txt,md}","**/.env*"],{cwd:c,ignore:["**/*.{spec,test}.{html,css,json,txt,md}","**/__tests__/**","**/__mocks__/**","**/tsconfig.json",".env.generated"],absolute:!1}),d=0,u=0;for(let e of w)try{let t=p(c,e),a=p(m,e);await o(l(a),{recursive:!0}),await s(t,a),d++}catch(t){u++,console.error(`Failed to copy ${n(r,p(c,e))}:`,t)}if(u>0)throw Error(`Failed to copy ${u} file(s)`);d>0&&a.debug(`Copied ${d} non-compilable file(s)`)};export const compileWithSwc=async(s,c)=>{let{sourceDir:m="src",outDir:w="./node_modules/.phoenix"}=s??{},g=c?.logger??d(),h=process.cwd(),_=p(h,m),x=p(h,w);await t(x);let y=await i(["**/*.{ts,tsx}"],{cwd:_,ignore:["**/*.{spec,test}.{ts,tsx}","**/__tests__/**","**/__mocks__/**"],absolute:!1}),b=u(),$=0,j=0;for(let t of y)try{let s=p(_,t),i=p(x,t.replace(/\.tsx?$/,".js")),c=await a(s,"utf-8"),n=await e(c,{...b,filename:s,sourceMaps:!0,outputPath:i});await o(l(i),{recursive:!0}),await r(i,n.code,"utf-8"),n.map&&await r(`${i}.map`,n.map,"utf-8"),$++}catch(e){j++,console.error(`Failed to compile ${n(h,p(_,t))}:`,e)}if(j>0)throw Error(`Failed to compile ${j} file(s)`);g.isDebug&&g.debug(`Compiled ${$} file(s) with swc`),await f(m,w,g)};let g=async(t,s,i)=>{let c=process.cwd(),n=p(c,s),m=p(c,i),w=u(),d=p(n,t),f=p(m,t.replace(/\.tsx?$/,".js")),g=await a(d,"utf-8"),h=await e(g,{...w,filename:d,sourceMaps:!0,outputPath:f});await o(l(f),{recursive:!0}),await r(f,h.code,"utf-8"),h.map&&await r(`${f}.map`,h.map,"utf-8")},h=async(e,t,a)=>{let r=process.cwd(),i=p(r,t),c=p(r,a),n=p(i,e),m=p(c,e);await o(l(m),{recursive:!0}),await s(n,m)};export const watchWithSwc=async(e,t)=>{let{sourceDir:s="src",outDir:o="./node_modules/.phoenix"}=e??{},a=t?.logger??d(),r=p(process.cwd(),s);a.isDebug&&a.step("Running initial SWC compilation"),await compileWithSwc({sourceDir:s,outDir:o},t),a.isDebug&&a.success("Initial compilation complete");let i=new m,c=w(["**/*.{ts,tsx,html,css,json,txt,md}","../.env*"],{cwd:r,ignored:["**/*.{spec,test}.{ts,tsx}","**/__tests__/**","**/cache.json"],persistent:!0,ignoreInitial:!0});return c.on("all",(e,t)=>{("change"===e||"add"===e)&&i.next({file:t,type:e})}),i.subscribe(async({file:e})=>{let t=n(r,p(r,e));try{e.match(/\.tsx?$/)?(await g(t,s,o),a.fileChanged(t)):(e.match(/\.(html|css|json|txt|md)$/)||e.match(/\.env/))&&(await h(t,s,o),a.fileChanged(t))}catch(e){a.error(`Failed to process ${t}`),a.isDebug&&console.error(e)}}),a.isDebug&&console.log(`
|
|
2
2
|
👀 Watching ${s} for changes...
|
|
3
3
|
`),{stop:async()=>{await c.close(),a.isDebug&&a.info("SWC watcher stopped")}}};
|
|
@@ -11,6 +11,8 @@ export { useSelectEntity, type UseSelectEntity, useSelectedEntity, type UseSelec
|
|
|
11
11
|
export { useActiveNavigationState, type UseActiveNavigationState, useActiveViewState, type UseActiveViewState, useSetViewState, type UseSetViewState, useActiveToolState, type UseActiveToolState, useIsFullscreenActive, type UseIsFullscreenActive, useSetFullscreen, type UseSetFullscreen, useActiveViewports, type UseActiveViewports, useDraggedDomainEntityId, type UseDraggedDomainEntityId, useNavigate, type UseNavigate, usePushNavigate, type UsePushNavigate, usePopNavigate, type UsePopNavigate, } from './system-hooks/navigation-hooks.js';
|
|
12
12
|
export { useComposition, type UseComposition } from './system-hooks/composition-hooks.js';
|
|
13
13
|
export { useUndoRedo, type UseUndoRedo, type UseUndoRedoResult } from './system-hooks/undo-redo-hooks.js';
|
|
14
|
+
export { useTabNavigation, type TabEntry, type TabSource, type UseTabNavigation, type UseTabNavigationOptions, type UseTabNavigationResult, } from './system-hooks/tab-navigation-hooks.js';
|
|
15
|
+
export { useStepNavigation, type NavigationStepEntry, type StepStatus, type UseStepNavigation, type UseStepNavigationResult, } from './system-hooks/step-navigation-hooks.js';
|
|
14
16
|
export { useHoveredDomainEntityId, useHoveredEntity, useHoveredEntityId, type UseHoveredDomainEntityId, type UseHoveredEntity, type UseHoveredEntityId, } from './system-hooks/hover-hooks.js';
|
|
15
17
|
export { type UseRootEntityState, useRootEntityState, useRootEntityRenderedState, } from './entity-hooks/use-root-entity-state.js';
|
|
16
18
|
export { type UseRootEntityId, useRootEntityId, useRootEntityRenderedId } from './entity-hooks/use-root-entity-id.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{useDomain}from"./common-hooks/use-domain-context.js";export{useStoreSelector,useRenderedSelector,useRenderedSelector$,useStoreSelector$}from"./common-hooks/use-store-selector.js";export{useEagerObservableSelector,usePatientObservableSelector,usePatientObservableValue,useEagerObservableValue}from"./common-hooks/use-observable-selector.js";export{useStateValidity,useMapViolationHints,useControlViolations,useFinalizeViolations}from"./common-hooks/use-state-validity.js";export{useConfiguratorMessages}from"./common-hooks/use-configurator-messages.js";export{useComponent,useRenderedComponent,useComponents,useDynamicComponents,useRenderedComponents,useRootComponents,useRootRenderedComponents,useFirstComponent,useFirstRenderedComponent,useFirstRootComponent,useFirstRootRenderedComponent}from"./component-hooks/use-component.js";export{useCommittedState,useCommittedSlice,useRenderedSlice,useRenderedState}from"./common-hooks/use-slices.js";export{useDomainEntityState,useDomainEntityRenderedState}from"./entity-hooks/use-domain-entity-state.js";export{useEntityPrice,useRootEntityPrice}from"./entity-hooks/use-entity-price.js";export{useSelectEntity,useSelectedEntity,useSelectedEntityId,useSelectedDomainEntityId,useDeselect,useSelectParent}from"./system-hooks/selection-hooks.js";export{useActiveNavigationState,useActiveViewState,useSetViewState,useActiveToolState,useIsFullscreenActive,useSetFullscreen,useActiveViewports,useDraggedDomainEntityId,useNavigate,usePushNavigate,usePopNavigate}from"./system-hooks/navigation-hooks.js";export{useComposition}from"./system-hooks/composition-hooks.js";export{useUndoRedo}from"./system-hooks/undo-redo-hooks.js";export{useHoveredDomainEntityId,useHoveredEntity,useHoveredEntityId}from"./system-hooks/hover-hooks.js";export{useRootEntityState,useRootEntityRenderedState}from"./entity-hooks/use-root-entity-state.js";export{useRootEntityId,useRootEntityRenderedId}from"./entity-hooks/use-root-entity-id.js";export{useEntitySelector,useRootEntitySelector,useRenderedEntitySelector,useRenderedRootEntitySelector}from"./common-hooks/use-entity-selector.js";export{useDimensionState,useDimensions,useDimensionConfig}from"./system-hooks/dimension-hooks.js";
|
|
1
|
+
export{useDomain}from"./common-hooks/use-domain-context.js";export{useStoreSelector,useRenderedSelector,useRenderedSelector$,useStoreSelector$}from"./common-hooks/use-store-selector.js";export{useEagerObservableSelector,usePatientObservableSelector,usePatientObservableValue,useEagerObservableValue}from"./common-hooks/use-observable-selector.js";export{useStateValidity,useMapViolationHints,useControlViolations,useFinalizeViolations}from"./common-hooks/use-state-validity.js";export{useConfiguratorMessages}from"./common-hooks/use-configurator-messages.js";export{useComponent,useRenderedComponent,useComponents,useDynamicComponents,useRenderedComponents,useRootComponents,useRootRenderedComponents,useFirstComponent,useFirstRenderedComponent,useFirstRootComponent,useFirstRootRenderedComponent}from"./component-hooks/use-component.js";export{useCommittedState,useCommittedSlice,useRenderedSlice,useRenderedState}from"./common-hooks/use-slices.js";export{useDomainEntityState,useDomainEntityRenderedState}from"./entity-hooks/use-domain-entity-state.js";export{useEntityPrice,useRootEntityPrice}from"./entity-hooks/use-entity-price.js";export{useSelectEntity,useSelectedEntity,useSelectedEntityId,useSelectedDomainEntityId,useDeselect,useSelectParent}from"./system-hooks/selection-hooks.js";export{useActiveNavigationState,useActiveViewState,useSetViewState,useActiveToolState,useIsFullscreenActive,useSetFullscreen,useActiveViewports,useDraggedDomainEntityId,useNavigate,usePushNavigate,usePopNavigate}from"./system-hooks/navigation-hooks.js";export{useComposition}from"./system-hooks/composition-hooks.js";export{useUndoRedo}from"./system-hooks/undo-redo-hooks.js";export{useTabNavigation}from"./system-hooks/tab-navigation-hooks.js";export{useStepNavigation}from"./system-hooks/step-navigation-hooks.js";export{useHoveredDomainEntityId,useHoveredEntity,useHoveredEntityId}from"./system-hooks/hover-hooks.js";export{useRootEntityState,useRootEntityRenderedState}from"./entity-hooks/use-root-entity-state.js";export{useRootEntityId,useRootEntityRenderedId}from"./entity-hooks/use-root-entity-id.js";export{useEntitySelector,useRootEntitySelector,useRenderedEntitySelector,useRenderedRootEntitySelector}from"./common-hooks/use-entity-selector.js";export{useDimensionState,useDimensions,useDimensionConfig}from"./system-hooks/dimension-hooks.js";
|