@salsita-ai/sdk 0.50.0-develop.6d8967bc99 → 0.50.0-develop.6fc83d7732
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/README.md +20 -15
- package/dist/admin/create-admin-pages-from-spec.d.ts +1 -1
- package/dist/admin/default-admin-elements.d.ts +9 -0
- package/dist/admin/default-admin-elements.js +1 -0
- package/dist/admin/default-elements/built-in-entity-element.d.ts +10 -0
- package/dist/admin/default-elements/built-in-entity-element.js +1 -0
- package/dist/admin/default-elements/built-in-layout-element.d.ts +10 -0
- package/dist/admin/default-elements/built-in-layout-element.js +1 -0
- package/dist/admin/default-elements/index.d.ts +2 -0
- package/dist/admin/default-elements/index.js +1 -0
- package/dist/admin/index.d.ts +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/application/index.d.ts +1 -0
- package/dist/application/index.js +1 -1
- package/dist/application/mount-application.d.ts +1 -1
- package/dist/application/mount-application.js +1 -1
- package/dist/application/mount-configurator.d.ts +19 -0
- package/dist/application/mount-configurator.js +1 -0
- package/dist/application/mounted-application-runtime.d.ts +4 -0
- package/dist/application/mounted-application-runtime.js +1 -0
- package/dist/application/register-web-component.d.ts +2 -0
- package/dist/application/register-web-component.js +1 -0
- package/dist/application-spec/caching/content-diff.js +1 -1
- package/dist/application-spec/core/application-file.d.ts +0 -2
- package/dist/application-spec/core/application-file.js +1 -1
- package/dist/application-spec/core/components/component-filter.d.ts +53 -0
- package/dist/application-spec/core/components/component-filter.js +1 -0
- package/dist/application-spec/core/components/index.d.ts +2 -0
- package/dist/application-spec/core/components/index.js +1 -0
- package/dist/application-spec/core/components/sdk-code-ref.d.ts +9 -0
- package/dist/application-spec/core/components/sdk-code-ref.js +1 -0
- package/dist/application-spec/core/enhanced-json-value.d.ts +0 -68
- package/dist/application-spec/core/enhanced-json-value.js +1 -1
- package/dist/application-spec/core/expressions/index.d.ts +2 -0
- package/dist/application-spec/core/expressions/index.js +1 -0
- package/dist/application-spec/core/expressions/relation-expression-params.d.ts +11 -0
- package/dist/application-spec/core/expressions/sdk-expression-implementation.d.ts +24 -0
- package/dist/application-spec/core/expressions/sdk-expression-implementation.js +1 -0
- package/dist/application-spec/core/index.d.ts +2 -0
- package/dist/application-spec/core/index.js +1 -1
- package/dist/application-spec/core/json-value.d.ts +1 -1
- package/dist/application-spec/enhanced-json/eval-to-constant.d.ts +14 -0
- package/dist/application-spec/enhanced-json/index.d.ts +4 -0
- package/dist/application-spec/enhanced-json/index.js +1 -0
- package/dist/application-spec/enhanced-json/is-enhanced-json-array.d.ts +2 -0
- package/dist/application-spec/enhanced-json/is-enhanced-json-array.js +1 -0
- package/dist/application-spec/enhanced-json/resolve-enhanced-json-value.d.ts +18 -0
- package/dist/application-spec/enhanced-json/resolve-enhanced-json-value.js +1 -0
- package/dist/application-spec/enhanced-json/schema-from-value.d.ts +24 -0
- package/dist/application-spec/enhanced-json/schema-from-value.js +1 -0
- package/dist/application-spec/enhanced-json/stringify-enhanced-json-value.d.ts +14 -0
- package/dist/application-spec/enhanced-json/stringify-enhanced-json-value.js +1 -0
- package/dist/application-spec/enhanced-json/validate-enhanced-json-value-caps.d.ts +12 -0
- package/dist/application-spec/enhanced-json/validate-enhanced-json-value-caps.js +1 -0
- package/dist/application-spec/enhanced-json/validate-no-legacy-code-ref.d.ts +13 -0
- package/dist/application-spec/enhanced-json/validate-no-legacy-code-ref.js +1 -0
- package/dist/application-spec/errors/diagnostic-codes.d.ts +23 -0
- package/dist/application-spec/errors/diagnostic-codes.js +1 -1
- package/dist/application-spec/errors/diagnostic-messages.d.ts +27 -1
- package/dist/application-spec/errors/diagnostic-messages.js +1 -1
- package/dist/application-spec/generate-application/code/common/code-imports.js +1 -1
- package/dist/application-spec/generate-application/code/common/env-reader.js +2 -2
- package/dist/application-spec/generate-application/code/common/index.d.ts +1 -0
- package/dist/application-spec/generate-application/code/common/index.js +1 -1
- package/dist/application-spec/generate-application/code/common/single-quoted-string.d.ts +6 -0
- package/dist/application-spec/generate-application/code/common/single-quoted-string.js +1 -0
- package/dist/application-spec/generate-application/code/domain-runtime-files.js +1 -1
- package/dist/application-spec/generate-application/code/generate-application-code.d.ts +3 -2
- package/dist/application-spec/generate-application/code/generate-application-code.js +1 -1
- package/dist/application-spec/generate-application/code/project-scaffolding.d.ts +22 -14
- package/dist/application-spec/generate-application/code/project-scaffolding.js +2 -2
- package/dist/application-spec/generate-application/code/standalone-app-files.d.ts +2 -2
- package/dist/application-spec/generate-application/code/standalone-app-files.js +1 -1
- package/dist/application-spec/generate-application/code/story-files.js +1 -1
- package/dist/application-spec/generate-application/code/templates/admin-config.js +3 -0
- package/dist/application-spec/generate-application/code/templates/application-types.js +6 -6
- package/dist/application-spec/generate-application/code/templates/common/doc-comment.d.ts +2 -0
- package/dist/application-spec/generate-application/code/templates/common/doc-comment.js +1 -0
- package/dist/application-spec/generate-application/code/templates/common/format-property-key.d.ts +7 -0
- package/dist/application-spec/generate-application/code/templates/common/format-property-key.js +1 -0
- package/dist/application-spec/generate-application/code/templates/common/schema-to-type-string.js +3 -3
- package/dist/application-spec/generate-application/code/templates/configurator-config.js +6 -4
- package/dist/application-spec/generate-application/code/templates/configurator-globals.d.ts +2 -1
- package/dist/application-spec/generate-application/code/templates/configurator-globals.js +19 -6
- package/dist/application-spec/generate-application/code/templates/domain-spec/factory-types/domain-factory-types.d.ts +35 -0
- package/dist/application-spec/generate-application/code/templates/domain-spec/factory-types/domain-factory-types.js +5 -0
- package/dist/application-spec/generate-application/code/templates/domain-spec/factory-types/index.d.ts +1 -0
- package/dist/application-spec/generate-application/code/templates/domain-spec/factory-types/index.js +1 -0
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-component-optics.d.ts +1 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-component-optics.js +7 -7
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-defaults.d.ts +4 -2
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-defaults.js +1 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-domain-block.js +72 -76
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-invariants.d.ts +9 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-invariants.js +1 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-phx-globals.js +3 -0
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-presets.js +1 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-relation-expressions.js +1 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-resources.d.ts +6 -8
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-resources.js +21 -21
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-states.d.ts +1 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec/generate-states.js +2 -2
- package/dist/application-spec/generate-application/code/templates/domain-spec/index.d.ts +3 -2
- package/dist/application-spec/generate-application/code/templates/domain-spec/index.js +1 -1
- package/dist/application-spec/generate-application/code/templates/domain-spec.js +12 -12
- package/dist/application-spec/generate-application/code/templates/domain.js +3 -3
- package/dist/application-spec/generate-application/code/templates/eslint-config.d.ts +5 -0
- package/dist/application-spec/generate-application/code/templates/eslint-config.js +4 -5
- package/dist/application-spec/generate-application/code/templates/frontend-config.js +3 -5
- package/dist/application-spec/generate-application/code/templates/gitignore.js +0 -3
- package/dist/application-spec/generate-application/code/templates/global-definition/generate-action-types.js +7 -7
- package/dist/application-spec/generate-application/code/templates/global-definition/generate-assistant-form-element-types.d.ts +12 -0
- package/dist/application-spec/generate-application/code/templates/global-definition/generate-assistant-form-element-types.js +7 -0
- package/dist/application-spec/generate-application/code/templates/global-definition/generate-phx-types.d.ts +43 -0
- package/dist/application-spec/generate-application/code/templates/global-definition/generate-phx-types.js +33 -0
- package/dist/application-spec/generate-application/code/templates/global-definition/index.d.ts +2 -0
- package/dist/application-spec/generate-application/code/templates/global-definition/index.js +1 -1
- package/dist/application-spec/generate-application/code/templates/globals.js +24 -43
- package/dist/application-spec/generate-application/code/templates/index.d.ts +3 -10
- package/dist/application-spec/generate-application/code/templates/index.js +1 -1
- package/dist/application-spec/generate-application/code/templates/lingui-rc.d.ts +5 -0
- package/dist/application-spec/generate-application/code/templates/lingui-rc.js +4 -4
- package/dist/application-spec/generate-application/code/templates/prettier-config.d.ts +5 -0
- package/dist/application-spec/generate-application/code/templates/prettier-config.js +4 -5
- package/dist/application-spec/generate-application/code/templates/root-tsconfig.d.ts +6 -0
- package/dist/application-spec/generate-application/code/templates/root-tsconfig.js +2 -0
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-cli-skill.js +1 -1
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-create-configurator-skill.js +66 -42
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-feedback-skill.js +5 -5
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-review-skill.js +9 -3
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill.js +1 -1
- package/dist/application-spec/generate-application/code/templates/skills/phoenix-variants-skill.js +2 -2
- package/dist/application-spec/generate-application/code/templates/skills/sdk-content.d.ts +42 -18
- package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +6 -8
- package/dist/application-spec/generate-application/code/templates/spec-code/collect-spec-code.d.ts +58 -0
- package/dist/application-spec/generate-application/code/templates/spec-code/collect-spec-code.js +1 -0
- package/dist/application-spec/generate-application/code/templates/spec-code/index.d.ts +2 -0
- package/dist/application-spec/generate-application/code/templates/spec-code/index.js +1 -0
- package/dist/application-spec/generate-application/code/templates/spec-code/spec-code.d.ts +18 -0
- package/dist/application-spec/generate-application/code/templates/spec-code/spec-code.js +41 -0
- package/dist/application-spec/generate-application/code/templates/spec-messages/index.d.ts +1 -0
- package/dist/application-spec/generate-application/code/templates/spec-messages/index.js +1 -0
- package/dist/application-spec/generate-application/code/templates/spec-messages/spec-messages.d.ts +20 -0
- package/dist/application-spec/generate-application/code/templates/spec-messages/spec-messages.js +6 -0
- package/dist/application-spec/generate-application/code/templates/story/phx-module.d.ts +8 -2
- package/dist/application-spec/generate-application/code/templates/story/phx-module.js +15 -3
- package/dist/application-spec/generate-application/code/templates/story/wrapper.d.ts +3 -0
- package/dist/application-spec/generate-application/code/templates/story/wrapper.js +2 -1
- package/dist/application-spec/generate-application/code/templates/tsconfig.d.ts +5 -0
- package/dist/application-spec/generate-application/code/templates/tsconfig.js +1 -1
- package/dist/application-spec/generate-application/code/templates/webhook-builders.js +4 -3
- package/dist/application-spec/generate-application/code/templates/webhook-index.d.ts +6 -1
- package/dist/application-spec/generate-application/code/templates/webhook-index.js +10 -1
- package/dist/application-spec/generate-application/code/templates/webhooks-config.js +2 -3
- package/dist/application-spec/generate-application/implementations/phx-application-types.d.ts +25 -0
- package/dist/application-spec/generate-application/implementations/phx-application-types.js +1 -0
- package/dist/application-spec/generate-application/implementations/phx-domain.d.ts +36 -0
- package/dist/application-spec/generate-application/implementations/phx-domain.js +1 -0
- package/dist/application-spec/generate-application/implementations/phx-model.d.ts +24 -0
- package/dist/application-spec/generate-application/implementations/phx-model.js +1 -0
- package/dist/application-spec/generate-application/implementations/phx.d.ts +22 -0
- package/dist/application-spec/generate-application/implementations/phx.js +1 -0
- package/dist/application-spec/generate-application/index.d.ts +4 -0
- package/dist/application-spec/generate-application/index.js +1 -1
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-model-runtime.d.ts +6 -4
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-model-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.d.ts +12 -4
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-spec-runtime.d.ts +16 -0
- package/dist/application-spec/generate-application/runtime/admin-runtime/admin-spec-runtime.js +1 -0
- package/dist/application-spec/generate-application/runtime/admin-runtime/states-runtime.d.ts +2 -2
- package/dist/application-spec/generate-application/runtime/application-runtime.d.ts +35 -10
- package/dist/application-spec/generate-application/runtime/application-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/application-spec-slice.d.ts +71 -0
- package/dist/application-spec/generate-application/runtime/application-spec-slice.js +1 -0
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-model-runtime.d.ts +7 -5
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-model-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.d.ts +12 -7
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-spec-runtime.d.ts +16 -0
- package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-spec-runtime.js +1 -0
- package/dist/application-spec/generate-application/runtime/configurator-runtime/states-runtime.d.ts +2 -2
- package/dist/application-spec/generate-application/runtime/create-application.d.ts +57 -0
- package/dist/application-spec/generate-application/runtime/create-application.js +1 -0
- package/dist/application-spec/generate-application/runtime/factory-data/factory-data-runtime.d.ts +43 -0
- package/dist/application-spec/generate-application/runtime/factory-data/factory-data-runtime.js +1 -0
- package/dist/application-spec/generate-application/runtime/factory-data/index.d.ts +3 -0
- package/dist/application-spec/generate-application/runtime/factory-data/index.js +1 -0
- package/dist/application-spec/generate-application/runtime/factory-data/recipe-defaults.d.ts +13 -0
- package/dist/application-spec/generate-application/runtime/factory-data/recipe-defaults.js +1 -0
- package/dist/application-spec/generate-application/runtime/factory-data/static-violations.d.ts +16 -0
- package/dist/application-spec/generate-application/runtime/factory-data/static-violations.js +1 -0
- package/dist/application-spec/generate-application/runtime/index.d.ts +8 -0
- package/dist/application-spec/generate-application/runtime/index.js +1 -0
- package/dist/application-spec/generate-application/runtime/migrations/index.d.ts +1 -0
- package/dist/application-spec/generate-application/runtime/migrations/index.js +1 -0
- package/dist/application-spec/generate-application/runtime/migrations/migration-runtime.d.ts +53 -0
- package/dist/application-spec/generate-application/runtime/migrations/migration-runtime.js +1 -0
- package/dist/application-spec/generate-application/runtime/sections/assistant-runtime.d.ts +2 -2
- package/dist/application-spec/generate-application/runtime/sections/build-sdk-optic.d.ts +3 -2
- package/dist/application-spec/generate-application/runtime/sections/build-sdk-optic.js +1 -1
- package/dist/application-spec/generate-application/runtime/sections/build-user-bound-optic.d.ts +2 -1
- package/dist/application-spec/generate-application/runtime/sections/build-user-bound-optic.js +1 -1
- package/dist/application-spec/generate-application/runtime/sections/component-optic.d.ts +5 -1
- package/dist/application-spec/generate-application/runtime/sections/component-optic.js +1 -1
- package/dist/application-spec/generate-application/runtime/sections/data-runtime.d.ts +4 -4
- package/dist/application-spec/generate-application/runtime/sections/expression-runtime.d.ts +15 -6
- package/dist/application-spec/generate-application/runtime/sections/expression-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/sections/generate-memo-eval-plan-runtime.d.ts +9 -5
- package/dist/application-spec/generate-application/runtime/sections/generate-memo-eval-plan-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/sections/reflection-constants-runtime.d.ts +3 -1
- package/dist/application-spec/generate-application/runtime/sections/reflection-constants-runtime.js +1 -1
- package/dist/application-spec/generate-application/runtime/sections/theme-runtime.d.ts +2 -2
- package/dist/application-spec/generate-application/runtime/sections/transformers-runtime.d.ts +4 -6
- package/dist/application-spec/generate-application/runtime/spec-code/index.d.ts +2 -0
- package/dist/application-spec/generate-application/runtime/spec-code/index.js +1 -0
- package/dist/application-spec/generate-application/runtime/spec-code/resolve-spec-code.d.ts +18 -0
- package/dist/application-spec/generate-application/runtime/spec-code/resolve-spec-code.js +1 -0
- package/dist/application-spec/generate-application/runtime/spec-code/spec-code-module.d.ts +17 -0
- package/dist/application-spec/generate-application/runtime/spec-runtime.d.ts +21 -0
- package/dist/application-spec/generate-application/runtime/spec-runtime.js +1 -0
- package/dist/application-spec/generate-application/runtime-spec/decode-runtime-spec.d.ts +9 -0
- package/dist/application-spec/generate-application/runtime-spec/decode-runtime-spec.js +1 -0
- package/dist/application-spec/generate-application/runtime-spec/encode-runtime-spec.d.ts +8 -0
- package/dist/application-spec/generate-application/runtime-spec/encode-runtime-spec.js +1 -0
- package/dist/application-spec/generate-application/runtime-spec/encoded-runtime-spec.d.ts +28 -0
- package/dist/application-spec/generate-application/runtime-spec/encoded-runtime-spec.js +1 -0
- package/dist/application-spec/generate-application/runtime-spec/index.d.ts +5 -0
- package/dist/application-spec/generate-application/runtime-spec/index.js +1 -0
- package/dist/application-spec/generate-application/runtime-spec/runtime-spec.d.ts +103 -0
- package/dist/application-spec/generate-application/runtime-spec/runtime-spec.js +1 -0
- package/dist/application-spec/generate-application/runtime-spec/spec-code-id.d.ts +40 -0
- package/dist/application-spec/generate-application/runtime-spec/spec-code-id.js +1 -0
- package/dist/application-spec/index.d.ts +1 -0
- package/dist/application-spec/index.js +1 -1
- package/dist/application-spec/parse-application-files/categorize/categorize-files.d.ts +34 -0
- package/dist/application-spec/parse-application-files/categorize/categorize-files.js +1 -1
- package/dist/application-spec/parse-application-files/categorize/earlier-generated-editor-configs.d.ts +16 -0
- package/dist/application-spec/parse-application-files/categorize/earlier-generated-editor-configs.js +9 -0
- package/dist/application-spec/parse-application-files/categorize/earlier-generated-tsconfig.d.ts +9 -0
- package/dist/application-spec/parse-application-files/categorize/earlier-generated-tsconfig.js +1 -0
- package/dist/application-spec/parse-application-files/categorize/module-analysis.d.ts +1 -1
- package/dist/application-spec/parse-application-files/categorize/module-analysis.js +1 -1
- package/dist/application-spec/parse-application-files/raw-spec/parse-yaml-spec-file.js +1 -1
- package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.d.ts +47 -4
- package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.js +1 -1
- package/dist/application-spec/parse-application-files/raw-spec/raw-spec-file.js +1 -1
- package/dist/application-spec/parse-application-files/spec/application/admin-spec/admin-spec.js +1 -1
- package/dist/application-spec/parse-application-files/spec/application/configurator-spec/configurator-spec.js +1 -1
- package/dist/application-spec/parse-application-files/spec/common/index.d.ts +1 -0
- package/dist/application-spec/parse-application-files/spec/common/index.js +1 -1
- package/dist/application-spec/parse-application-files/spec/common/value-type/format-value-type.d.ts +3 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/format-value-type.js +1 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/index.d.ts +5 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/index.js +1 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/intersect-value-types.d.ts +6 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/intersect-value-types.js +1 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/is-assignable-value-type.d.ts +8 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/is-assignable-value-type.js +1 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/value-type-at.d.ts +10 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/value-type-at.js +1 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/value-type.d.ts +98 -0
- package/dist/application-spec/parse-application-files/spec/common/value-type/value-type.js +1 -0
- package/dist/application-spec/parse-application-files/spec/components/bindings/component-path.d.ts +13 -0
- package/dist/application-spec/parse-application-files/spec/components/bindings/component-path.js +1 -0
- package/dist/application-spec/parse-application-files/spec/components/bindings/index.d.ts +2 -0
- package/dist/application-spec/parse-application-files/spec/components/bindings/index.js +1 -1
- package/dist/application-spec/parse-application-files/spec/components/bindings/read-binding-type.d.ts +24 -0
- package/dist/application-spec/parse-application-files/spec/components/bindings/read-binding-type.js +1 -0
- package/dist/application-spec/parse-application-files/spec/components/component-entry.d.ts +1 -9
- package/dist/application-spec/parse-application-files/spec/components/component-entry.js +1 -1
- package/dist/application-spec/parse-application-files/spec/components/component-filter.d.ts +1 -54
- package/dist/application-spec/parse-application-files/spec/components/component-filter.js +1 -1
- package/dist/application-spec/parse-application-files/spec/components/component-state-type.d.ts +8 -0
- package/dist/application-spec/parse-application-files/spec/components/component-state-type.js +1 -0
- package/dist/application-spec/parse-application-files/spec/components/filter-criterion-types.d.ts +13 -0
- package/dist/application-spec/parse-application-files/spec/components/filter-criterion-types.js +1 -0
- package/dist/application-spec/parse-application-files/spec/components/index.d.ts +1 -1
- package/dist/application-spec/parse-application-files/spec/components/index.js +1 -1
- package/dist/application-spec/parse-application-files/spec/components/static-entity-id.d.ts +14 -0
- package/dist/application-spec/parse-application-files/spec/components/static-entity-id.js +1 -0
- package/dist/application-spec/parse-application-files/spec/components/user-component-entry.d.ts +6 -0
- package/dist/application-spec/parse-application-files/spec/components/user-component-entry.js +1 -1
- package/dist/application-spec/parse-application-files/spec/components/user-components.d.ts +7 -4
- package/dist/application-spec/parse-application-files/spec/components/user-components.js +1 -1
- package/dist/application-spec/parse-application-files/spec/expressions/expression-read-types.d.ts +24 -0
- package/dist/application-spec/parse-application-files/spec/expressions/expression-read-types.js +1 -0
- package/dist/application-spec/parse-application-files/spec/expressions/expression-read.d.ts +13 -0
- package/dist/application-spec/parse-application-files/spec/expressions/expression-value-type.d.ts +9 -0
- package/dist/application-spec/parse-application-files/spec/expressions/expression-value-type.js +1 -0
- package/dist/application-spec/parse-application-files/spec/expressions/expression.d.ts +1 -28
- package/dist/application-spec/parse-application-files/spec/expressions/expression.js +1 -1
- package/dist/application-spec/parse-application-files/spec/expressions/expressions.d.ts +7 -3
- package/dist/application-spec/parse-application-files/spec/expressions/expressions.js +1 -1
- package/dist/application-spec/parse-application-files/spec/expressions/index.d.ts +0 -1
- package/dist/application-spec/parse-application-files/spec/expressions/index.js +1 -1
- package/dist/application-spec/parse-application-files/spec/expressions/memo-eval-plan.js +1 -1
- package/dist/application-spec/parse-application-files/spec/expressions/user-expressions.d.ts +3 -1
- package/dist/application-spec/parse-application-files/spec/expressions/user-expressions.js +1 -1
- package/dist/application-spec/parse-application-files/spec/invariants/invariants.d.ts +2 -2
- package/dist/application-spec/parse-application-files/spec/invariants/invariants.js +1 -1
- package/dist/application-spec/parse-application-files/spec/model/defaults.d.ts +7 -2
- package/dist/application-spec/parse-application-files/spec/model/defaults.js +1 -1
- package/dist/application-spec/parse-application-files/spec/model/index.d.ts +1 -0
- package/dist/application-spec/parse-application-files/spec/model/index.js +1 -1
- package/dist/application-spec/parse-application-files/spec/model/model-type-resolution.d.ts +10 -0
- package/dist/application-spec/parse-application-files/spec/model/model-type-resolution.js +1 -0
- package/dist/application-spec/parse-application-files/spec/relations/reference-field-type.d.ts +25 -0
- package/dist/application-spec/parse-application-files/spec/relations/reference-field-type.js +1 -0
- package/dist/application-spec/parse-application-files/spec/relations/relation-field-mismatch.d.ts +46 -0
- package/dist/application-spec/parse-application-files/spec/relations/relation-field-mismatch.js +1 -0
- package/dist/application-spec/parse-application-files/spec/relations/relations.d.ts +4 -3
- package/dist/application-spec/parse-application-files/spec/relations/relations.js +1 -1
- package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-form-elements.d.ts +9 -0
- package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-form-elements.js +1 -0
- package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.d.ts +4 -1
- package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.js +1 -1
- package/dist/application-spec/parse-application-files/spec/sections/infra.d.ts +2 -0
- package/dist/application-spec/parse-application-files/spec/sections/infra.js +1 -1
- package/dist/client/authorized-client.d.ts +3 -1
- package/dist/client/authorized-client.js +1 -1
- package/dist/client/contract.d.ts +34 -0
- package/dist/client/contract.js +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +1 -1
- package/dist/client/local-authorized-client.d.ts +3 -1
- package/dist/client/local-authorized-client.js +1 -1
- package/dist/client/memory-authorized-client.d.ts +3 -1
- package/dist/client/memory-authorized-client.js +1 -1
- package/dist/client/phoenix-api.d.ts +797 -121
- package/dist/configurator/ConfiguratorApp.js +1 -1
- 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/index.d.ts +1 -0
- package/dist/configurator/app-loader-content/index.js +1 -0
- package/dist/configurator/application-runtime/extensions/create-configurator-extensions.js +1 -1
- package/dist/configurator/application-runtime/extensions/selectors.d.ts +3 -1
- package/dist/configurator/application-runtime/extensions/selectors.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.d.ts +130 -5
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.d.ts +39 -7
- package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/camera-control-system/camera-control-system.d.ts +1 -1
- package/dist/configurator/application-runtime/extensions/systems/canvas-system.d.ts +8 -0
- package/dist/configurator/application-runtime/extensions/systems/canvas-system.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/drag-and-drop-system/drag-and-drop-system.d.ts +3 -3
- package/dist/configurator/application-runtime/extensions/systems/export-system/export-busy-error.d.ts +8 -0
- package/dist/configurator/application-runtime/extensions/systems/export-system/export-busy-error.js +1 -0
- package/dist/configurator/application-runtime/extensions/systems/export-system/export-system.d.ts +23 -1
- package/dist/configurator/application-runtime/extensions/systems/export-system/export-system.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/close-unclosed-braces.d.ts +12 -0
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/close-unclosed-braces.js +1 -0
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/entity-meta.d.ts +14 -1
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/entity-meta.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.d.ts +64 -7
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/inject-assistant-lenses.d.ts +11 -0
- package/dist/configurator/application-runtime/extensions/systems/graphql-system/inject-assistant-lenses.js +1 -0
- package/dist/configurator/application-runtime/extensions/systems/image-rendering-system/image-rendering-system.d.ts +14 -5
- package/dist/configurator/application-runtime/extensions/systems/image-rendering-system/image-rendering-system.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/index.d.ts +2 -2
- package/dist/configurator/application-runtime/extensions/systems/layout-system/layout-system.d.ts +2 -2
- package/dist/configurator/application-runtime/extensions/systems/layout-system/layout-system.js +1 -1
- package/dist/configurator/common/configurator-interface.d.ts +145 -5
- package/dist/configurator/common/configurator-interface.js +1 -1
- package/dist/configurator/common/index.d.ts +3 -1
- package/dist/configurator/common/index.js +1 -1
- package/dist/configurator/common/use-reset-camera.d.ts +6 -0
- package/dist/configurator/common/use-reset-camera.js +1 -0
- package/dist/configurator/configuration-not-found-page/BuiltInConfigurationNotFoundPage.d.ts +5 -0
- package/dist/configurator/configuration-not-found-page/BuiltInConfigurationNotFoundPage.js +1 -0
- package/dist/configurator/configuration-not-found-page/ConfigurationNotFoundPageElement.d.ts +6 -0
- package/dist/configurator/configuration-not-found-page/ConfigurationNotFoundPageElement.js +1 -0
- package/dist/configurator/configuration-not-found-page/index.d.ts +2 -0
- package/dist/configurator/configuration-not-found-page/index.js +1 -0
- package/dist/configurator/configurator-context/ConfiguratorResponsiveProvider.d.ts +12 -0
- package/dist/configurator/configurator-context/ConfiguratorResponsiveProvider.js +1 -0
- package/dist/configurator/configurator-context/PresetSelectionProvider.js +1 -1
- 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 +11 -1
- package/dist/configurator/configurator-context/configurator-routing/ConfiguratorRoutingProvider.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 +7 -0
- package/dist/configurator/configurator-error-page/ConfiguratorErrorPageElement.js +1 -0
- package/dist/configurator/configurator-error-page/index.d.ts +2 -0
- package/dist/configurator/configurator-error-page/index.js +1 -0
- package/dist/configurator/index.d.ts +10 -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/ConfiguratorNotFoundPage.js +1 -1
- package/dist/configurator/pages/DefaultConfiguratorLayout.d.ts +7 -0
- package/dist/configurator/pages/DefaultConfiguratorLayout.js +1 -0
- package/dist/configurator/pages/DefaultNotAuthenticatedPage.d.ts +8 -0
- package/dist/configurator/pages/DefaultNotAuthenticatedPage.js +1 -0
- package/dist/configurator/pages/DefaultPresetUi.d.ts +7 -0
- package/dist/configurator/pages/DefaultPresetUi.js +1 -0
- package/dist/configurator/pages/LoadedConfiguratorDetailPage.js +1 -1
- package/dist/configurator/pages/NewConfiguratorPage.js +1 -1
- package/dist/configurator/pages/PresetCreationErrorPage.d.ts +1 -1
- package/dist/configurator/pages/PresetCreationErrorPage.js +1 -1
- package/dist/configurator/pages/RootPage.js +1 -1
- package/dist/configurator/pages/configurator-layout-name.d.ts +2 -0
- package/dist/configurator/pages/configurator-layout-name.js +1 -0
- package/dist/configurator/pages/default-configurator-elements.d.ts +13 -0
- package/dist/configurator/pages/default-configurator-elements.js +1 -0
- package/dist/configurator/pages/default-elements/built-in-entity-element.d.ts +10 -0
- package/dist/configurator/pages/default-elements/built-in-entity-element.js +1 -0
- package/dist/configurator/pages/default-elements/built-in-layout-element.d.ts +10 -0
- package/dist/configurator/pages/default-elements/built-in-layout-element.js +1 -0
- package/dist/configurator/pages/default-elements/domain-control-elements.d.ts +6 -0
- package/dist/configurator/pages/default-elements/domain-control-elements.js +1 -0
- package/dist/configurator/pages/default-elements/index.d.ts +5 -0
- package/dist/configurator/pages/default-elements/index.js +1 -0
- package/dist/configurator/pages/default-elements/recipe-panel-elements.d.ts +3 -0
- package/dist/configurator/pages/default-elements/recipe-panel-elements.js +1 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInAddEntityFab.d.ts +3 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInAddEntityFab.js +1 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInDeleteEntityFab.d.ts +3 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInDeleteEntityFab.js +1 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInNavigationHotspot.d.ts +3 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInNavigationHotspot.js +1 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInSelectionHotspot.d.ts +3 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInSelectionHotspot.js +1 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInToolMenuFab.d.ts +3 -0
- package/dist/configurator/pages/default-elements/scene-controls/BuiltInToolMenuFab.js +1 -0
- package/dist/configurator/pages/default-elements/scene-controls/index.d.ts +6 -0
- package/dist/configurator/pages/default-elements/scene-controls/index.js +1 -0
- package/dist/configurator/pages/default-elements/scene-controls/scene-control-elements.d.ts +6 -0
- package/dist/configurator/pages/default-elements/scene-controls/scene-control-elements.js +1 -0
- package/dist/configurator/pages/default-elements/sidebar-footer-elements.d.ts +7 -0
- package/dist/configurator/pages/default-elements/sidebar-footer-elements.js +1 -0
- package/dist/configurator/pages/default-elements/toolbar-elements.d.ts +7 -0
- package/dist/configurator/pages/default-elements/toolbar-elements.js +1 -0
- package/dist/configurator/pages/error-boundaries/GeneralErrorBoundary.js +1 -1
- package/dist/configurator/pages/error-boundaries/LoadingErrorBoundary.js +1 -1
- package/dist/configurator/pages/index.d.ts +1 -0
- package/dist/configurator/pages/index.js +1 -1
- package/dist/configurator/preset-screen-elements/PresetDeviceNotSupportedElement.d.ts +4 -0
- package/dist/configurator/preset-screen-elements/PresetDeviceNotSupportedElement.js +1 -0
- package/dist/configurator/preset-screen-elements/PresetPageActionElement.d.ts +4 -0
- package/dist/configurator/preset-screen-elements/PresetPageActionElement.js +1 -0
- package/dist/configurator/preset-screen-elements/index.d.ts +2 -0
- package/dist/configurator/preset-screen-elements/index.js +1 -0
- package/dist/configurator/scene/index.d.ts +1 -0
- package/dist/configurator/scene/index.js +1 -1
- package/dist/configurator/scene/scene-controls/DeleteEntityFab.js +1 -1
- package/dist/configurator/scene/scene-controls/SelectionHotspot.js +1 -1
- package/dist/configurator/scene/scene-controls/ToolFab.d.ts +4 -0
- package/dist/configurator/scene/scene-controls/ToolFab.js +1 -1
- package/dist/configurator/scene/scene-controls/hotspot-drag-handle/HotspotDragHandle.d.ts +1 -1
- package/dist/configurator/scene/scene-controls/hotspot-drag-handle/HotspotDragSceneHandle.d.ts +1 -1
- package/dist/configurator/scene/scene-controls/use-scene-control-tooltip.d.ts +2 -1
- package/dist/configurator/scene/scene-entity/dimension-indicators/DimensionIndicator.d.ts +1 -1
- package/dist/configurator/scene/scene-entity/dimension-indicators/DimensionIndicatorInternal.d.ts +1 -1
- package/dist/configurator/scene/scene-entity/drag-and-drop/shared.d.ts +2 -2
- package/dist/configurator/scene/scene-entity/drag-and-drop/use-drag-object.d.ts +1 -1
- package/dist/configurator/scene/scene-entity/materials/RemoteReplacedMaterial.js +1 -1
- package/dist/configurator/scene/scene-entity/objects/SceneEntityObjectTextDecal.d.ts +1 -1
- package/dist/configurator/scene/scene-entity/scene-controls/SceneEntityHtmlHotspots.js +1 -1
- package/dist/configurator/toolbar-actions/index.d.ts +3 -0
- package/dist/configurator/toolbar-actions/index.js +1 -0
- package/dist/configurator/toolbar-actions/use-assistant-panel.d.ts +18 -0
- package/dist/configurator/toolbar-actions/use-assistant-panel.js +1 -0
- package/dist/configurator/toolbar-actions/use-measurements.d.ts +35 -0
- package/dist/configurator/toolbar-actions/use-measurements.js +1 -0
- package/dist/configurator/toolbar-actions/use-toolbar-actions.d.ts +100 -0
- package/dist/configurator/toolbar-actions/use-toolbar-actions.js +1 -0
- package/dist/configurator/toolbar-actions/use-toolbar-help.d.ts +7 -0
- package/dist/configurator/toolbar-actions/use-toolbar-help.js +1 -0
- package/dist/configurator/ui/assistant/AssistantChips.d.ts +10 -3
- package/dist/configurator/ui/assistant/AssistantChips.js +1 -1
- package/dist/configurator/ui/assistant/AssistantComposer.d.ts +2 -1
- package/dist/configurator/ui/assistant/AssistantComposer.js +1 -1
- package/dist/configurator/ui/assistant/AssistantMarkdown.d.ts +7 -2
- package/dist/configurator/ui/assistant/AssistantMarkdown.js +1 -1
- package/dist/configurator/ui/assistant/AssistantPanel.js +1 -1
- package/dist/configurator/ui/assistant/AssistantThread.d.ts +6 -2
- package/dist/configurator/ui/assistant/AssistantThread.js +1 -1
- package/dist/configurator/ui/assistant/alert/AssistantAlert.d.ts +8 -0
- package/dist/configurator/ui/assistant/alert/AssistantAlert.js +1 -0
- package/dist/configurator/ui/assistant/alert/index.d.ts +2 -0
- package/dist/configurator/ui/assistant/alert/index.js +1 -0
- package/dist/configurator/ui/assistant/alert/remark-alerts.d.ts +10 -0
- package/dist/configurator/ui/assistant/alert/remark-alerts.js +1 -0
- package/dist/configurator/ui/assistant/entity-link/AssistantEntityLink.d.ts +13 -0
- package/dist/configurator/ui/assistant/entity-link/AssistantEntityLink.js +1 -0
- package/dist/configurator/ui/assistant/entity-link/entity-link-selection.d.ts +22 -0
- package/dist/configurator/ui/assistant/entity-link/entity-link-selection.js +1 -0
- package/dist/configurator/ui/assistant/entity-link/entity-link-state.d.ts +27 -0
- package/dist/configurator/ui/assistant/entity-link/entity-link-state.js +1 -0
- package/dist/configurator/ui/assistant/entity-link/index.d.ts +2 -0
- package/dist/configurator/ui/assistant/entity-link/index.js +1 -0
- package/dist/configurator/ui/assistant/form/AssistantForm.d.ts +19 -0
- package/dist/configurator/ui/assistant/form/AssistantForm.js +1 -0
- package/dist/configurator/ui/assistant/form/compose-answer.d.ts +11 -0
- package/dist/configurator/ui/assistant/form/compose-answer.js +1 -0
- package/dist/configurator/ui/assistant/form/describe-build-errors.d.ts +6 -0
- package/dist/configurator/ui/assistant/form/describe-build-errors.js +1 -0
- package/dist/configurator/ui/assistant/form/form-model.d.ts +37 -0
- package/dist/configurator/ui/assistant/form/form-model.js +1 -0
- package/dist/configurator/ui/assistant/form/form-state.d.ts +4 -0
- package/dist/configurator/ui/assistant/form/form-state.js +1 -0
- package/dist/configurator/ui/assistant/form/index.d.ts +4 -0
- package/dist/configurator/ui/assistant/form/index.js +1 -0
- package/dist/configurator/ui/assistant/form/parse-form-block.d.ts +10 -0
- package/dist/configurator/ui/assistant/form/parse-form-block.js +1 -0
- package/dist/configurator/ui/assistant/form/read-form-attributes.d.ts +13 -0
- package/dist/configurator/ui/assistant/form/read-form-attributes.js +1 -0
- package/dist/configurator/ui/assistant/form/registry/built-in-form-elements.d.ts +3 -0
- package/dist/configurator/ui/assistant/form/registry/built-in-form-elements.js +1 -0
- package/dist/configurator/ui/assistant/form/registry/create-assistant-form-element.d.ts +31 -0
- package/dist/configurator/ui/assistant/form/registry/create-assistant-form-element.js +1 -0
- package/dist/configurator/ui/assistant/form/registry/form-element-definition.d.ts +16 -0
- package/dist/configurator/ui/assistant/form/registry/form-element-definition.js +1 -0
- package/dist/configurator/ui/assistant/form/registry/form-registry.d.ts +31 -0
- package/dist/configurator/ui/assistant/form/registry/form-registry.js +1 -0
- package/dist/configurator/ui/assistant/form/registry/index.d.ts +6 -0
- package/dist/configurator/ui/assistant/form/registry/index.js +1 -0
- package/dist/configurator/ui/assistant/form/registry/use-form-registry.d.ts +8 -0
- package/dist/configurator/ui/assistant/form/registry/use-form-registry.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/FormCard.d.ts +9 -0
- package/dist/configurator/ui/assistant/form-elements/FormCard.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/FormConfirm.d.ts +9 -0
- package/dist/configurator/ui/assistant/form-elements/FormConfirm.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/FormGroup.d.ts +6 -0
- package/dist/configurator/ui/assistant/form-elements/FormGroup.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/FormNumber.d.ts +9 -0
- package/dist/configurator/ui/assistant/form-elements/FormNumber.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/FormOptions.d.ts +6 -0
- package/dist/configurator/ui/assistant/form-elements/FormOptions.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/FormReply.d.ts +6 -0
- package/dist/configurator/ui/assistant/form-elements/FormReply.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/FormSwatch.d.ts +10 -0
- package/dist/configurator/ui/assistant/form-elements/FormSwatch.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/FormText.d.ts +6 -0
- package/dist/configurator/ui/assistant/form-elements/FormText.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/clamp-form-number.d.ts +10 -0
- package/dist/configurator/ui/assistant/form-elements/clamp-form-number.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/form-element-props.d.ts +38 -0
- package/dist/configurator/ui/assistant/form-elements/form-element-props.js +1 -0
- package/dist/configurator/ui/assistant/form-elements/index.d.ts +9 -0
- package/dist/configurator/ui/assistant/form-elements/index.js +1 -0
- package/dist/configurator/ui/assistant/index.d.ts +3 -0
- package/dist/configurator/ui/assistant/index.js +1 -1
- package/dist/configurator/ui/assistant/reference-link/AssistantReferenceLink.d.ts +16 -0
- package/dist/configurator/ui/assistant/reference-link/AssistantReferenceLink.js +1 -0
- package/dist/configurator/ui/assistant/reference-link/assistant-link-target.d.ts +34 -0
- package/dist/configurator/ui/assistant/reference-link/assistant-link-target.js +1 -0
- package/dist/configurator/ui/assistant/reference-link/assistant-reference-details.d.ts +25 -0
- package/dist/configurator/ui/assistant/reference-link/assistant-reference-details.js +1 -0
- package/dist/configurator/ui/assistant/reference-link/index.d.ts +3 -0
- package/dist/configurator/ui/assistant/reference-link/index.js +1 -0
- package/dist/configurator/ui/assistant/reference-link/reference-styles.d.ts +12 -0
- package/dist/configurator/ui/assistant/reference-link/reference-styles.js +1 -0
- package/dist/configurator/ui/assistant/reference-link/use-assistant-reference-details.d.ts +8 -0
- package/dist/configurator/ui/assistant/reference-link/use-assistant-reference-details.js +1 -0
- package/dist/configurator/ui/assistant/resource-image/AssistantImage.d.ts +9 -0
- package/dist/configurator/ui/assistant/resource-image/AssistantImage.js +1 -0
- package/dist/configurator/ui/assistant/resource-image/index.d.ts +3 -0
- package/dist/configurator/ui/assistant/resource-image/index.js +1 -0
- package/dist/configurator/ui/assistant/resource-image/resource-path.d.ts +2 -0
- package/dist/configurator/ui/assistant/resource-image/resource-path.js +1 -0
- package/dist/configurator/ui/assistant/resource-image/use-resource-image-url.d.ts +5 -0
- package/dist/configurator/ui/assistant/resource-image/use-resource-image-url.js +1 -0
- package/dist/configurator/ui/assistant/screenshots/ScreenshotThumbnails.d.ts +9 -0
- package/dist/configurator/ui/assistant/screenshots/ScreenshotThumbnails.js +1 -0
- package/dist/configurator/ui/assistant/screenshots/index.d.ts +2 -0
- package/dist/configurator/ui/assistant/screenshots/index.js +1 -0
- package/dist/configurator/ui/assistant/screenshots/tool-screenshots.d.ts +14 -0
- package/dist/configurator/ui/assistant/screenshots/tool-screenshots.js +1 -0
- package/dist/configurator/ui/assistant/streamed-markdown.d.ts +4 -2
- package/dist/configurator/ui/assistant/streamed-markdown.js +2 -2
- package/dist/configurator/ui/assistant/thread-items.d.ts +21 -3
- package/dist/configurator/ui/assistant/thread-items.js +1 -1
- package/dist/configurator/ui/assistant/tool-chip.js +1 -1
- package/dist/configurator/ui/assistant/use-follow-stream.js +1 -1
- package/dist/configurator/ui/configurator-layout/sidebar-keys/index.d.ts +1 -0
- package/dist/configurator/ui/configurator-layout/sidebar-keys/index.js +1 -0
- package/dist/configurator/ui/configurator-layout/sidebar-keys/sidebar-keys.d.ts +11 -0
- package/dist/configurator/ui/configurator-layout/sidebar-keys/sidebar-keys.js +1 -0
- 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/configurator-layout/slots/desktop-sidebar/DesktopSidebar.d.ts +101 -0
- package/dist/configurator/ui/configurator-layout/slots/desktop-sidebar/DesktopSidebar.js +16 -0
- package/dist/configurator/ui/configurator-layout/slots/desktop-sidebar/index.d.ts +1 -0
- package/dist/configurator/ui/configurator-layout/slots/desktop-sidebar/index.js +1 -0
- 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/services/InvariantViolations.d.ts +4 -4
- package/dist/configurator/ui/configurator-layout/slots/services/InvariantViolations.js +1 -1
- package/dist/configurator/ui/configurator-layout/slots/services/Services.js +1 -1
- package/dist/configurator/ui/configurator-layout/slots/services/SideEffects.d.ts +3 -6
- package/dist/configurator/ui/configurator-layout/slots/services/SideEffects.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/slots/slots.d.ts +1 -0
- package/dist/configurator/ui/configurator-layout/slots/slots.js +1 -1
- package/dist/configurator/ui/configurator-layout/universal/DesktopLayout.d.ts +0 -74
- package/dist/configurator/ui/configurator-layout/universal/DesktopLayout.js +5 -20
- package/dist/configurator/ui/configurator-layout/universal/DesktopPreview.js +1 -1
- package/dist/configurator/ui/configurator-layout/universal/ExportingOverlay.d.ts +5 -0
- package/dist/configurator/ui/configurator-layout/universal/ExportingOverlay.js +1 -0
- package/dist/configurator/ui/configurator-layout/universal/Layout.js +1 -1
- package/dist/configurator/ui/configurator-layout/universal/MobileLayout.js +4 -4
- package/dist/configurator/ui/configurator-layout/universal/MobilePreview.js +1 -1
- package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.d.ts +47 -0
- package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.js +1 -0
- package/dist/configurator/ui/configurator-layout/universal/use-scene-safe-area.d.ts +11 -26
- package/dist/configurator/ui/configurator-layout/universal/use-scene-safe-area.js +1 -1
- package/dist/configurator/ui/index.d.ts +1 -0
- package/dist/configurator/ui/index.js +1 -1
- package/dist/configurator/ui/legacy/layout/common/CommonFinalizeButton.js +1 -1
- package/dist/configurator/ui/legacy/layout/common/CommonSummaryButton.js +1 -1
- package/dist/configurator/ui/legacy/layout/common/CommonTopNavigation.js +1 -1
- package/dist/configurator/ui/legacy/layout/common/ReactiveInterfaceLayout.js +1 -1
- package/dist/configurator/ui/legacy/layout/common/ResponsiveFinalizeButton.js +1 -1
- package/dist/configurator/ui/legacy/layout/common/SummaryReadLayout.js +1 -1
- package/dist/configurator/ui/legacy/layout/simple/SimpleOwnerLayout.js +1 -1
- package/dist/configurator/ui/legacy/layout/simple/SimpleTopNavigation.js +1 -1
- package/dist/configurator/ui/legacy/layout/steps/StepsNavigation.d.ts +2 -2
- package/dist/configurator/ui/legacy/layout/steps/StepsNavigation.js +1 -1
- package/dist/configurator/ui/legacy/layout/steps/StepsOwnerLayout.js +1 -1
- package/dist/configurator/ui/legacy/layout/steps/StepsSidebarFooter.js +1 -1
- package/dist/configurator/ui/legacy/layout/tabs/TabsOwnerLayout.js +1 -1
- package/dist/configurator/ui/legacy/toolbar/common/DimensionUnitSelector.js +1 -1
- package/dist/configurator/ui/legacy/toolbar/common/MeasurementsIndicatorsSwitch.js +1 -1
- package/dist/configurator/ui/legacy/toolbar/desktop-toolbar/FullscreenToolbarAction.js +1 -1
- package/dist/configurator/ui/legacy/toolbar/desktop-toolbar/HelpToolbarAction.js +1 -1
- package/dist/configurator/ui/legacy/toolbar/desktop-toolbar/MeasurementsToolbarAction.js +1 -1
- package/dist/configurator/ui/legacy/toolbar/desktop-toolbar/UndoRedoToolbarGroup.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/AppLoader.d.ts +0 -7
- package/dist/configurator/ui/phoenix-layout-elements/AppLoader.js +2 -6
- package/dist/configurator/ui/phoenix-layout-elements/AssistantToolbarButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/EnterFullscreenToolbarButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/ExitFullscreenToolbarButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/HelpToolbarButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/LanguageMenuAction.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/LanguageToolbarButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/LayoutCustomOwnerUi.d.ts +1 -0
- package/dist/configurator/ui/phoenix-layout-elements/LayoutCustomOwnerUi.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/LayoutCustomReadUi.d.ts +1 -0
- package/dist/configurator/ui/phoenix-layout-elements/LayoutCustomReadUi.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/MeasurementsMenuAction.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/MeasurementsToolbarButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/RedoToolbarButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/ShareToolbarButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/UndoToolbarButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/header/common/back-button/use-go-back-action.js +1 -1
- 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/index.d.ts +8 -1
- package/dist/configurator/ui/phoenix-layout-elements/index.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/measurements/DimensionUnitSelector.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/measurements/MeasurementsIndicatorsSwitch.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/share/ShareModal.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/share/use-share.d.ts +7 -8
- package/dist/configurator/ui/phoenix-layout-elements/share/use-share.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/basic/BasicFinalizeButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/CustomFinalizeButton.js +1 -1
- package/dist/configurator/ui/phoenix-layout-elements/sidebar-footer/common/EnterSummaryButton.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/configurator/ui/preset-layout/CategoryPresetUi.js +1 -1
- package/dist/configurator/ui/preset-layout/PresetUi.d.ts +4 -2
- package/dist/configurator/ui/preset-layout/PresetUi.js +1 -1
- package/dist/configurator/ui/preset-layout/index.d.ts +6 -1
- package/dist/configurator/ui/preset-layout/index.js +1 -1
- package/dist/configurator/ui/save-message-snackbars/SaveMessageSnackbars.d.ts +10 -0
- package/dist/configurator/ui/save-message-snackbars/SaveMessageSnackbars.js +1 -0
- package/dist/configurator/ui/save-message-snackbars/index.d.ts +1 -0
- package/dist/configurator/ui/save-message-snackbars/index.js +1 -0
- package/dist/docs/complex-use-cases/filtered-controls-with-fix-dialog.mdx +16 -19
- package/dist/docs/complex-use-cases/focus-mode.mdx +2 -0
- package/dist/docs/complex-use-cases/sketching.mdx +0 -38
- package/dist/docs/controls/select-attribute/cardselect.mdx +9 -13
- package/dist/docs/controls/select-child-entity/cardselect.mdx +1 -0
- package/dist/docs/dimensions/3d-scene-integration/dimension-indicators.mdx +2 -2
- package/dist/docs/dimensions/3d-scene-integration/editable-angle-indicator.mdx +1 -1
- package/dist/docs/dimensions/3d-scene-integration/editable-distance-dimension-indicator.mdx +1 -1
- package/dist/docs/dimensions/3d-scene-integration/list-selector-dimension-indicator.mdx +1 -1
- package/dist/docs/drag-and-drop/drag-handle.mdx +1 -1
- package/dist/docs/drag-and-drop/hotspot-handle.mdx +1 -1
- package/dist/docs/drag-and-drop/planes.mdx +1 -1
- package/dist/docs/drag-and-drop/rotatable.mdx +2 -2
- package/dist/docs/fabs/basic.mdx +6 -2
- package/dist/docs/index.json +136 -1
- package/dist/docs/integrations/webcomponent-api/basics.mdx +2 -0
- package/dist/docs/integrations/woocommerce/local-development.mdx +1 -1
- package/dist/docs/layouts/custom-layout.mdx +2597 -0
- package/dist/docs/layouts/gallery/ardent-compressor.mdx +3516 -0
- package/dist/docs/layouts/gallery/corvid-drone.mdx +3428 -0
- package/dist/docs/layouts/gallery/fenwick-window.mdx +2803 -0
- package/dist/docs/layouts/gallery/ferrule-bar-station.mdx +6567 -0
- package/dist/docs/layouts/gallery/kestrel-kettle.mdx +1858 -0
- package/dist/docs/layouts/gallery/nightjar-guitar.mdx +3698 -0
- package/dist/docs/layouts/gallery/sunfold-solar.mdx +2683 -0
- package/dist/docs/layouts/gallery/tidepool-aquascape.mdx +5203 -0
- package/dist/docs/layouts/header/fixed.mdx +2 -2
- package/dist/docs/layouts/header/floating.mdx +2 -2
- package/dist/docs/layouts/sidebar/in-a-custom-layout.mdx +380 -0
- package/dist/docs/layouts/sidebar-footer/basic.mdx +32 -77
- package/dist/docs/other-features/client-side-rendering/declarative.mdx +2 -2
- package/dist/docs/other-features/error-5xx.mdx +1 -1
- package/dist/docs/other-features/navigation-hotspot.mdx +2 -0
- package/dist/docs/other-features/not-found-404.mdx +1 -1
- package/dist/docs/other-features/side-effects.mdx +8 -11
- package/dist/docs/other-features/transformers.mdx +4 -0
- package/dist/docs/other-features/violation-messages.mdx +52 -56
- package/dist/docs/phoenix/application-states.mdx +9 -7
- package/dist/docs/phoenix/clerk-sign-in.mdx +1 -1
- package/dist/docs/phoenix/components.mdx +6 -0
- package/dist/docs/phoenix/configurator-interface.mdx +402 -81
- package/dist/docs/phoenix/deployments.mdx +3 -7
- package/dist/docs/phoenix/domain-model.mdx +6 -0
- package/dist/docs/phoenix/expression-context.mdx +11 -3
- package/dist/docs/phoenix/finalization.mdx +1 -1
- package/dist/docs/phoenix/getting-started.mdx +7 -4
- package/dist/docs/phoenix/layout-slots.mdx +116 -0
- package/dist/docs/phoenix/localization.mdx +10 -6
- package/dist/docs/phoenix/migration-guide.mdx +24 -11
- package/dist/docs/phoenix/phoenix-cli.mdx +112 -65
- package/dist/docs/phoenix/project-structure.mdx +80 -36
- package/dist/docs/phoenix/quickstart.mdx +9 -9
- package/dist/docs/phoenix/sdk-builder-workflow.mdx +19 -17
- package/dist/docs/phoenix/sessions-and-access.mdx +2 -0
- package/dist/docs/phoenix/testing.mdx +148 -0
- package/dist/docs/phoenix/webhook-membership.mdx +10 -9
- package/dist/docs/phoenix/webhooks.mdx +23 -6
- package/dist/docs/selection-and-highlight/hotspots.mdx +2 -0
- package/dist/docs/ux-guidelines/choosing-a-control.mdx +11 -0
- package/dist/domain/admin-domain/implementation/common-admin-expressions.d.ts +26 -11
- package/dist/domain/admin-domain/implementation/common-admin-publish.d.ts +1 -2
- package/dist/domain/admin-domain/index.d.ts +8 -3
- package/dist/domain/admin-domain/index.js +1 -1
- package/dist/domain/application/GetApplicationWrapperHandler.d.ts +2 -2
- package/dist/domain/application/GetThemeHandler.d.ts +1 -1
- package/dist/domain/application/GetWebComponentOptions.d.ts +1 -1
- package/dist/domain/application/admin-page-factories.d.ts +1 -2
- package/dist/domain/application/application-config.d.ts +9 -26
- package/dist/domain/application/assistant/assistant-fields.d.ts +12 -0
- package/dist/domain/application/assistant/assistant-fields.js +1 -0
- package/dist/domain/application/assistant/assistant-manifest.d.ts +27 -2
- package/dist/domain/application/assistant/assistant-resolved-manifest.d.ts +46 -3
- package/dist/domain/application/assistant/assistant-resolved-manifest.js +1 -1
- package/dist/domain/application/assistant/assistant-value-type.js +1 -1
- package/dist/domain/application/assistant/build-assistant-lenses.d.ts +14 -0
- package/dist/domain/application/assistant/build-assistant-lenses.js +5 -0
- package/dist/domain/application/assistant/build-assistant-manifest.js +1 -1
- package/dist/domain/application/assistant/create-assistant-execution-schema.d.ts +32 -0
- package/dist/domain/application/assistant/create-assistant-execution-schema.js +1 -0
- package/dist/domain/application/assistant/form/assistant-form-element-name.d.ts +11 -0
- package/dist/domain/application/assistant/form/assistant-form-element-name.js +8 -0
- package/dist/domain/application/assistant/form/assistant-form-elements.d.ts +174 -0
- package/dist/domain/application/assistant/form/assistant-form-elements.js +1 -0
- package/dist/domain/application/assistant/form/assistant-resource-path.d.ts +10 -0
- package/dist/domain/application/assistant/form/assistant-resource-path.js +1 -0
- package/dist/domain/application/assistant/form/index.d.ts +4 -0
- package/dist/domain/application/assistant/form/index.js +1 -0
- package/dist/domain/application/assistant/generate-assistant-prompt.d.ts +4 -11
- package/dist/domain/application/assistant/generate-assistant-prompt.js +1 -1
- package/dist/domain/application/assistant/index.d.ts +8 -2
- package/dist/domain/application/assistant/index.js +1 -1
- package/dist/domain/application/assistant/session-setup/AssistantSetupHandler.d.ts +20 -0
- package/dist/domain/application/assistant/session-setup/AssistantSetupHandler.js +1 -0
- package/dist/domain/application/assistant/session-setup/apply-assistant-setup.d.ts +10 -0
- package/dist/domain/application/assistant/session-setup/apply-assistant-setup.js +1 -0
- package/dist/domain/application/assistant/session-setup/assistant-setup-manifest.d.ts +50 -0
- package/dist/domain/application/assistant/session-setup/assistant-setup-manifest.js +1 -0
- package/dist/domain/application/assistant/session-setup/create-session-assistant.d.ts +15 -0
- package/dist/domain/application/assistant/session-setup/create-session-assistant.js +1 -0
- package/dist/domain/application/assistant/session-setup/index.d.ts +4 -0
- package/dist/domain/application/assistant/session-setup/index.js +1 -0
- package/dist/domain/application/assistant-mutations.d.ts +2 -1
- package/dist/domain/application/assistant-mutations.js +1 -1
- package/dist/domain/application/assistant-recipe-groups.d.ts +4 -2
- package/dist/domain/application/configurator-builder.d.ts +11 -2
- package/dist/domain/application/configurator-builder.js +1 -1
- package/dist/domain/application/create-initial-published-data.d.ts +6 -7
- package/dist/domain/application/index.d.ts +3 -7
- package/dist/domain/application/index.js +1 -1
- package/dist/domain/application/published-data/DataRowValidatorHandler.d.ts +1 -1
- package/dist/domain/application/published-data/FilterPublishedChunksHandler.d.ts +1 -1
- package/dist/domain/application/published-data/MapPublishedChunkedDataHandler.d.ts +1 -1
- package/dist/domain/application/published-data/MapPublishedRevisionDataHandler.d.ts +1 -1
- package/dist/domain/application/published-data/SheetValidatorHandler.d.ts +1 -1
- package/dist/domain/application/published-data/build-legend-data.d.ts +1 -1
- package/dist/domain/application/published-data/format-field-type.d.ts +1 -1
- package/dist/domain/application/published-data/map-data-to-sheet.d.ts +1 -1
- package/dist/domain/application/published-data/map-published-chunked-data.d.ts +3 -3
- package/dist/domain/application/published-data/map-published-data.d.ts +7 -3
- package/dist/domain/application/published-data/validate-sheet-data.d.ts +1 -1
- package/dist/domain/application/standard-properties.js +1 -1
- package/dist/domain/application/web-component-property.d.ts +3 -1
- package/dist/domain/application/web-component-property.js +1 -1
- package/dist/domain/available-system-components.d.ts +1 -1
- package/dist/domain/available-system-components.js +1 -1
- package/dist/domain/core/action/action-factory.d.ts +11 -0
- package/dist/domain/core/action/action.d.ts +71 -0
- package/dist/domain/core/action/action.js +1 -0
- package/dist/domain/core/action/index.d.ts +2 -0
- package/dist/domain/core/action/index.js +1 -0
- package/dist/domain/core/component/domain-component-optic.d.ts +29 -40
- package/dist/domain/core/component/domain-component-optic.js +2 -2
- package/dist/domain/core/component/legacy-filter.d.ts +2 -17
- package/dist/domain/core/component/system-component-filter.d.ts +36 -18
- package/dist/domain/core/data-schema/data-cell-value.d.ts +4 -0
- package/dist/domain/core/data-schema/data-cell-value.js +1 -0
- package/dist/domain/core/data-schema/data-schema-array-field.d.ts +5 -0
- package/dist/domain/core/data-schema/data-schema-array-field.js +1 -0
- package/dist/domain/core/data-schema/data-schema-field-object.d.ts +4 -0
- package/dist/domain/core/data-schema/data-schema-field-object.js +1 -0
- package/dist/domain/core/data-schema/data-schema-field.d.ts +2 -0
- package/dist/domain/core/data-schema/data-schema-field.js +1 -0
- package/dist/domain/core/data-schema/data-schema-localized-field.d.ts +4 -0
- package/dist/domain/core/data-schema/data-schema-localized-field.js +1 -0
- package/dist/domain/core/data-schema/data-schema-primitive-field.d.ts +5 -0
- package/dist/domain/core/data-schema/data-schema-primitive-field.js +1 -0
- package/dist/domain/core/data-schema/data-schema.d.ts +2 -0
- package/dist/domain/core/data-schema/data-schema.js +1 -0
- package/dist/domain/core/data-schema/index.d.ts +7 -0
- package/dist/domain/core/data-schema/index.js +1 -0
- package/dist/domain/core/domain-factory.d.ts +1 -1
- package/dist/domain/core/domain.d.ts +2 -2
- package/dist/domain/core/emission/index.d.ts +8 -0
- package/dist/domain/core/emission/index.js +1 -0
- package/dist/domain/core/emission/reset-state-emission.d.ts +12 -0
- package/dist/domain/core/emission/reset-state-emission.js +1 -0
- package/dist/domain/core/emission/state-emission.d.ts +4 -0
- package/dist/domain/core/emission/state-emission.js +1 -0
- package/dist/domain/core/emission/state-update-emission.d.ts +14 -0
- package/dist/domain/core/emission/state-update-emission.js +1 -0
- package/dist/domain/core/emission/state-validation-result.d.ts +18 -0
- package/dist/domain/core/emission/state-validation-result.js +1 -0
- package/dist/domain/core/emission/transformation-log.d.ts +2 -0
- package/dist/domain/core/emission/transformation-log.js +1 -0
- package/dist/domain/core/emission/transformer-log-entry.d.ts +6 -0
- package/dist/domain/core/emission/transformer-log-entry.js +1 -0
- package/dist/domain/core/emission/transformer-log.d.ts +11 -0
- package/dist/domain/core/emission/transformer-log.js +1 -0
- package/dist/domain/core/emission/transformer-phase.d.ts +1 -0
- package/dist/domain/core/emission/transformer-phase.js +1 -0
- package/dist/domain/core/expression/aliased-expressions.d.ts +10 -0
- package/dist/domain/core/expression/aliased-expressions.js +1 -0
- package/dist/domain/core/expression/constant-expressions.d.ts +16 -8
- package/dist/domain/core/expression/constant-expressions.js +1 -1
- package/dist/domain/core/expression/custom-expression.d.ts +21 -8
- package/dist/domain/core/expression/custom-expression.js +1 -1
- package/dist/domain/core/expression/expression.d.ts +15 -17
- package/dist/domain/core/expression/index.d.ts +1 -2
- package/dist/domain/core/expression/index.js +1 -1
- package/dist/domain/core/index.d.ts +5 -1
- package/dist/domain/core/index.js +1 -1
- package/dist/domain/core/invariant/custom-invariants.d.ts +8 -16
- package/dist/domain/core/invariant/index.d.ts +1 -1
- package/dist/domain/core/invariant/index.js +1 -1
- package/dist/domain/core/invariant/invariants.d.ts +4 -70
- package/dist/domain/core/invariant/invariants.js +1 -1
- package/dist/domain/core/invariant/violation.d.ts +51 -3
- package/dist/domain/core/invariant/violation.js +1 -1
- package/dist/domain/core/model/model.d.ts +1 -1
- package/dist/domain/core/recipe/index.d.ts +1 -0
- package/dist/domain/core/recipe/index.js +1 -0
- package/dist/domain/core/recipe/recipes.d.ts +23 -0
- package/dist/domain/core/recipe/recipes.js +1 -0
- package/dist/domain/core/relation/all-of-type-relation.d.ts +19 -0
- package/dist/domain/core/relation/all-of-type-relation.js +1 -0
- package/dist/domain/core/relation/component-factories.d.ts +14 -0
- package/dist/domain/core/relation/component-factories.js +1 -0
- package/dist/domain/core/relation/composed-of.d.ts +12 -0
- package/dist/domain/core/relation/composed-of.js +1 -0
- package/dist/domain/core/relation/direct-relation.d.ts +17 -0
- package/dist/domain/core/relation/index.d.ts +8 -0
- package/dist/domain/core/relation/index.js +1 -0
- package/dist/domain/core/relation/indirect-relation.d.ts +25 -0
- package/dist/domain/core/relation/indirect-relation.js +1 -0
- package/dist/domain/core/relation/reference-component.d.ts +17 -0
- package/dist/domain/core/relation/reference-component.js +1 -0
- package/dist/domain/core/relation/reference-field.d.ts +7 -0
- package/dist/domain/core/relation/reference-field.js +1 -0
- package/dist/domain/core/relation/relation-factory.d.ts +22 -0
- package/dist/domain/core/relation/relation-factory.js +1 -0
- package/dist/domain/core/schema/extended-schema-validator.js +1 -1
- package/dist/domain/core/state/migrations.d.ts +14 -45
- package/dist/domain/core/state/migrations.js +1 -1
- package/dist/domain/core/state/state.d.ts +1 -1
- package/dist/domain/domain-builder.d.ts +11 -8
- package/dist/domain/domain-builder.js +1 -1
- package/dist/domain/domain-context/action-system/action-system.d.ts +2 -2
- package/dist/domain/domain-context/action-system/action-system.js +1 -1
- package/dist/domain/domain-context/action-system/index.d.ts +1 -0
- package/dist/domain/domain-context/action-system/index.js +1 -0
- package/dist/domain/domain-context/analytics-system/index.d.ts +2 -0
- package/dist/domain/domain-context/analytics-system/index.js +1 -0
- package/dist/domain/domain-context/create-transformer.d.ts +1 -1
- package/dist/domain/domain-context/dimension-system/dimension-system.d.ts +2 -2
- package/dist/domain/domain-context/dimension-system/dimension-system.js +1 -1
- package/dist/domain/domain-context/dimension-system/index.d.ts +1 -0
- package/dist/domain/domain-context/dimension-system/index.js +1 -0
- package/dist/domain/domain-context/domain-context-systems.d.ts +7 -7
- package/dist/domain/domain-context/index.d.ts +8 -18
- package/dist/domain/domain-context/index.js +1 -1
- package/dist/domain/domain-context/navigation-system/index.d.ts +3 -0
- package/dist/domain/domain-context/navigation-system/index.js +1 -0
- package/dist/domain/domain-context/navigation-system/navigation-system.d.ts +2 -3
- package/dist/domain/domain-context/navigation-system/navigation-system.js +1 -1
- package/dist/domain/domain-context/navigation-system/selection-transformers.d.ts +1 -1
- package/dist/domain/domain-context/recipe-system/index.d.ts +1 -0
- package/dist/domain/domain-context/recipe-system/index.js +1 -0
- package/dist/domain/domain-context/recipe-system/recipe-cooker-system.d.ts +2 -3
- package/dist/domain/domain-context/recipe-system/recipe-cooker-system.js +1 -1
- package/dist/domain/domain-context/relation-transformers/cascade-deletes.d.ts +2 -2
- package/dist/domain/domain-context/relation-transformers/cascade-ref-pruning.d.ts +2 -2
- package/dist/domain/domain-context/relation-transformers/cascade-ref-pruning.js +1 -1
- package/dist/domain/domain-context/relation-transformers/create-entity-refs.d.ts +2 -2
- package/dist/domain/domain-context/relation-transformers/create-entity-refs.js +2 -2
- package/dist/domain/domain-context/relation-transformers/index.d.ts +4 -0
- package/dist/domain/domain-context/relation-transformers/index.js +1 -0
- package/dist/domain/domain-context/relation-transformers/replace-refs.d.ts +2 -2
- package/dist/domain/domain-context/relation-transformers/replace-refs.js +1 -1
- package/dist/domain/domain-context/store-system/index.d.ts +4 -0
- package/dist/domain/domain-context/store-system/index.js +1 -0
- package/dist/domain/domain-context/store-system/reconcile-projected-entities.js +1 -1
- package/dist/domain/domain-context/store-system/store-system.d.ts +7 -35
- package/dist/domain/domain-context/store-system/store-system.js +2 -2
- package/dist/domain/domain-context/store-system/system-state.d.ts +1 -1
- package/dist/domain/domain-context/transformers/actions-builder.js +1 -1
- package/dist/domain/domain-context/transformers/auto-snap-transformer.d.ts +5 -7
- package/dist/domain/domain-context/transformers/auto-snap-transformer.js +1 -1
- package/dist/domain/domain-context/transformers/chain-builder.d.ts +1 -1
- package/dist/domain/domain-context/transformers/context.d.ts +2 -3
- package/dist/domain/domain-context/transformers/context.js +1 -1
- package/dist/domain/domain-context/transformers/entity-builder.js +1 -1
- package/dist/domain/domain-context/transformers/index.d.ts +1 -1
- package/dist/domain/domain-context/transformers/index.js +1 -1
- package/dist/domain/domain-context/transformers/when-builder.d.ts +2 -3
- package/dist/domain/domain-context/transformers/when-builder.js +1 -1
- package/dist/domain/domain-context/undo-system.d.ts +29 -3
- package/dist/domain/domain-context/undo-system.js +1 -1
- package/dist/domain/domain-runtime/access/selectors.d.ts +9 -0
- package/dist/domain/domain-runtime/access/selectors.js +1 -1
- package/dist/domain/domain-runtime/access/types.d.ts +2 -0
- package/dist/domain/domain-runtime/access/types.js +1 -1
- package/dist/domain/domain-runtime/application.d.ts +39 -0
- package/dist/domain/domain-runtime/application.js +1 -0
- package/dist/domain/domain-runtime/domain-context/action-context.d.ts +11 -0
- package/dist/domain/domain-runtime/domain-context/action-context.js +1 -0
- package/dist/domain/domain-runtime/domain-context/domain-context.js +1 -1
- package/dist/domain/domain-runtime/domain-context/expression-context.d.ts +8 -0
- package/dist/domain/domain-runtime/domain-context/expression-context.js +1 -0
- package/dist/domain/domain-runtime/domain-context/index.d.ts +3 -0
- package/dist/domain/domain-runtime/domain-context/runtime-expression-context.d.ts +18 -0
- package/dist/domain/domain-runtime/domain-context/runtime-expression-context.js +1 -0
- package/dist/domain/domain-runtime/domain-context/selectors.d.ts +20 -23
- package/dist/domain/domain-runtime/domain-context/selectors.js +1 -1
- package/dist/domain/domain-runtime/domain-context/types.d.ts +4 -1
- package/dist/domain/domain-runtime/domain-runtime.d.ts +128 -72
- package/dist/domain/domain-runtime/domain-runtime.js +1 -1
- package/dist/domain/domain-runtime/environment/initialize.d.ts +1 -1
- package/dist/domain/domain-runtime/environment/types.d.ts +1 -1
- package/dist/domain/domain-runtime/errors/implementation-error-collector.d.ts +21 -0
- package/dist/domain/domain-runtime/errors/implementation-error-collector.js +1 -0
- package/dist/domain/domain-runtime/errors/implementation-error.d.ts +46 -0
- package/dist/domain/domain-runtime/errors/implementation-error.js +1 -0
- package/dist/domain/domain-runtime/errors/implementation-errors.d.ts +13 -0
- package/dist/domain/domain-runtime/errors/implementation-errors.js +1 -0
- package/dist/domain/domain-runtime/errors/index.d.ts +4 -0
- package/dist/domain/domain-runtime/errors/index.js +1 -0
- package/dist/domain/domain-runtime/errors/to-implementation-errors.d.ts +10 -0
- package/dist/domain/domain-runtime/errors/to-implementation-errors.js +1 -0
- package/dist/domain/domain-runtime/index.d.ts +2 -0
- package/dist/domain/domain-runtime/index.js +1 -1
- package/dist/domain/domain-runtime/spec/create-violation-expressions.d.ts +24 -0
- package/dist/domain/domain-runtime/spec/create-violation-expressions.js +1 -0
- package/dist/domain/domain-runtime/spec/index.d.ts +5 -0
- package/dist/domain/domain-runtime/spec/index.js +1 -1
- package/dist/domain/domain-runtime/spec/initialize.d.ts +11 -0
- package/dist/domain/domain-runtime/spec/initialize.js +1 -1
- package/dist/domain/domain-runtime/spec/memoize-expression.d.ts +16 -0
- package/dist/domain/domain-runtime/spec/module-checks/element-registration.d.ts +125 -0
- package/dist/domain/domain-runtime/spec/module-checks/element-registration.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/find-assistant-form-element-errors.d.ts +12 -0
- package/dist/domain/domain-runtime/spec/module-checks/find-assistant-form-element-errors.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/handler-registration.d.ts +34 -0
- package/dist/domain/domain-runtime/spec/module-checks/handler-registration.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/index.d.ts +10 -0
- package/dist/domain/domain-runtime/spec/module-checks/index.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/merge-expression-modules.d.ts +23 -0
- package/dist/domain/domain-runtime/spec/module-checks/merge-expression-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/project-modules.d.ts +32 -0
- package/dist/domain/domain-runtime/spec/module-checks/project-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-action-modules.d.ts +4 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-action-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-component-optic-modules.d.ts +4 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-component-optic-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-expression-modules.d.ts +14 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-expression-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-imported-transformers.d.ts +22 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-imported-transformers.js +1 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-invariant-modules.d.ts +6 -0
- package/dist/domain/domain-runtime/spec/module-checks/resolve-invariant-modules.js +1 -0
- package/dist/domain/domain-runtime/spec/prepare-action-implementations.d.ts +15 -0
- package/dist/domain/domain-runtime/spec/prepare-action-implementations.js +5 -0
- package/dist/domain/domain-runtime/spec/prepare-domain-expressions.d.ts +16 -0
- package/dist/domain/domain-runtime/spec/selectors.d.ts +8 -7
- package/dist/domain/domain-runtime/spec/selectors.js +1 -1
- package/dist/domain/domain-runtime/spec/types.d.ts +5 -2
- package/dist/domain/domain-runtime/web-component/actions.d.ts +3 -0
- package/dist/domain/domain-runtime/web-component/actions.js +1 -1
- package/dist/domain/domain-runtime/web-component/initialize.d.ts +1 -0
- package/dist/domain/domain-runtime/web-component/initialize.js +1 -1
- package/dist/domain/domain-runtime/web-component/selectors.d.ts +2 -0
- package/dist/domain/domain-runtime/web-component/selectors.js +1 -1
- package/dist/domain/domain-runtime/web-component/types.d.ts +6 -3
- package/dist/domain/extensions/attribute-switch.d.ts +8 -0
- package/dist/domain/extensions/attribute-switch.js +1 -0
- package/dist/domain/extensions/canvas-config.d.ts +87 -0
- package/dist/domain/extensions/child-entity-recipe-hierarchy.d.ts +21 -0
- package/dist/domain/extensions/child-entity-recipe-hierarchy.js +1 -0
- package/dist/domain/extensions/common/angle-indicator-schema.d.ts +292 -0
- package/dist/domain/extensions/common/angle-indicator-schema.js +1 -0
- package/dist/domain/extensions/common/dimension-indicator-schema.d.ts +607 -0
- package/dist/domain/extensions/common/index.d.ts +4 -0
- package/dist/domain/extensions/common/index.js +1 -0
- package/dist/domain/extensions/custom-header.d.ts +11 -0
- package/dist/domain/extensions/custom-header.js +1 -0
- package/dist/domain/extensions/custom-price-footer.js +1 -0
- package/dist/domain/extensions/custom-sidebar-footer.d.ts +11 -0
- package/dist/domain/extensions/custom-sidebar-footer.js +1 -0
- package/dist/domain/extensions/editable-angle-indicator.d.ts +39 -0
- package/dist/domain/extensions/editable-angle-indicator.js +1 -0
- package/dist/domain/extensions/editable-distance-dimension-indicator.d.ts +43 -0
- package/dist/domain/extensions/editable-distance-dimension-indicator.js +1 -0
- package/dist/domain/extensions/editable-offset-indicator.d.ts +42 -0
- package/dist/domain/extensions/editable-offset-indicator.js +1 -0
- package/dist/domain/extensions/editable-size-indicator.d.ts +39 -0
- package/dist/domain/extensions/editable-size-indicator.js +1 -0
- package/dist/domain/extensions/entity-header.d.ts +22 -0
- package/dist/domain/extensions/entity-header.js +1 -0
- package/dist/domain/extensions/export-images/agent-screenshot.d.ts +24 -0
- package/dist/domain/extensions/export-images/agent-screenshot.js +1 -0
- package/dist/domain/extensions/export-images/client-side-image.d.ts +14 -0
- package/dist/domain/extensions/export-images/client-side-image.js +1 -0
- package/dist/domain/extensions/export-images/index.d.ts +3 -0
- package/dist/domain/extensions/export-images/index.js +1 -0
- package/dist/domain/extensions/export-images/save-template-thumbnail.d.ts +14 -0
- package/dist/domain/extensions/export-images/save-template-thumbnail.js +1 -0
- package/dist/domain/extensions/finalize-component.js +1 -0
- package/dist/domain/extensions/fit-selection-orthographic-camera-component.js +1 -0
- package/dist/domain/extensions/fit-selection-perspective-camera-component.js +1 -0
- package/dist/domain/extensions/horizontal-tabs.d.ts +28 -0
- package/dist/domain/extensions/index.d.ts +68 -0
- package/dist/domain/extensions/index.js +1 -0
- package/dist/domain/extensions/list-selector-dimension-indicator.d.ts +31 -0
- package/dist/domain/extensions/list-selector-dimension-indicator.js +1 -0
- package/dist/domain/extensions/navigation-steps.d.ts +19 -0
- package/dist/domain/extensions/offset-indicator.js +1 -0
- package/dist/domain/extensions/property-page.d.ts +16 -0
- package/dist/domain/extensions/property-page.js +1 -0
- package/dist/domain/extensions/replace-entity-recipe-hierarchy.d.ts +21 -0
- package/dist/domain/extensions/replace-entity-recipe-hierarchy.js +1 -0
- package/dist/domain/extensions/rotation-handle.d.ts +53 -0
- package/dist/domain/extensions/scene-entity-add-fab.d.ts +32 -0
- package/dist/domain/extensions/scene-entity-add-fab.js +1 -0
- package/dist/domain/extensions/scene-entity-animation.js +1 -0
- package/dist/domain/extensions/scene-entity-delete-fab.d.ts +19 -0
- package/dist/domain/extensions/scene-entity-delete-fab.js +1 -0
- package/dist/domain/extensions/scene-entity-html-hotspot.d.ts +27 -0
- package/dist/domain/extensions/scene-entity-html-hotspot.js +1 -0
- package/dist/domain/extensions/scene-entity-navigation-hotspot.d.ts +20 -0
- package/dist/domain/extensions/scene-entity-navigation-hotspot.js +1 -0
- package/dist/domain/extensions/scene-entity-object-html-hotspot.d.ts +31 -0
- package/dist/domain/extensions/scene-entity-object-html-hotspot.js +1 -0
- package/dist/domain/extensions/scene-entity-select-hotspot.d.ts +5 -0
- package/dist/domain/extensions/scene-entity-select-hotspot.js +1 -0
- package/dist/domain/extensions/sketching-edit.d.ts +28 -0
- package/dist/domain/extensions/sketching-edit.js +1 -0
- package/dist/domain/extensions/summary-component.js +1 -0
- package/dist/domain/extensions/tool-menu-fab.d.ts +40 -0
- package/dist/domain/extensions/tool-menu-fab.js +1 -0
- package/dist/domain/extensions/toolbar.d.ts +13 -0
- package/dist/domain/extensions/toolbar.js +1 -0
- package/dist/domain/index.d.ts +2 -8
- package/dist/domain/index.js +1 -1
- package/dist/domain/mocks/create-mock-domain-context.d.ts +1 -1
- package/dist/domain/mocks/create-mock-domain-runtime-services.d.ts +1 -1
- package/dist/domain/mocks/create-mock-domain-runtime.d.ts +37 -19
- package/dist/domain/mocks/create-mock-domain-runtime.js +1 -1
- package/dist/domain/mocks/create-mock-domain.d.ts +1 -1
- package/dist/domain/mocks/create-mock-domain.js +1 -1
- package/dist/domain/mocks/create-mock-state.d.ts +2 -2
- package/dist/domain/mocks/create-test-application-config.d.ts +59 -0
- package/dist/domain/mocks/create-test-application-config.js +1 -0
- package/dist/domain/mocks/index.d.ts +1 -0
- package/dist/domain/mocks/index.js +1 -1
- package/dist/domain/projections/blender-projections/blender-projections.d.ts +1 -2
- package/dist/domain/projections/blender-projections/camera.d.ts +2 -2
- package/dist/domain/projections/collision-projections.d.ts +1 -1
- package/dist/domain/projections/composition-projections.d.ts +1 -1
- package/dist/domain/projections/composition-projections.js +1 -1
- package/dist/domain/projections/constraint-projections.d.ts +1 -1
- package/dist/domain/projections/index.d.ts +1 -1
- package/dist/domain/projections/position-projections.d.ts +1 -1
- package/dist/domain/projections/projected-entities-projections.d.ts +1 -1
- package/dist/domain/projections/snapping-projections.d.ts +1 -1
- package/dist/domain/projections/validation-projections.d.ts +3 -19
- package/dist/domain/sketching/sketching-projections.d.ts +1 -1
- package/dist/domain/system-components/application-config.d.ts +2 -2
- package/dist/domain/system-components/child-entity-selection.d.ts +1 -2
- package/dist/domain/system-components/child-entity-selection.js +1 -1
- package/dist/domain/system-components/collision-box-geometry.d.ts +4 -2
- package/dist/domain/system-components/collision-geometry.d.ts +4 -2
- package/dist/domain/system-components/collision-point-geometry.d.ts +4 -2
- package/dist/domain/system-components/common/index.d.ts +0 -4
- package/dist/domain/system-components/common/index.js +1 -1
- package/dist/domain/system-components/common/material-override-schema.d.ts +2 -2
- package/dist/domain/system-components/common/recipes.d.ts +21 -4
- package/dist/domain/system-components/common/recipes.js +1 -1
- package/dist/domain/system-components/common/scene-entity-control-schema.d.ts +3 -0
- package/dist/domain/system-components/common/scene-entity-control-schema.js +1 -1
- package/dist/domain/system-components/constraint.d.ts +10 -23
- package/dist/domain/system-components/drag-handle.d.ts +1 -1
- package/dist/domain/system-components/drag-handle.js +1 -1
- package/dist/domain/system-components/fit-entity-orthographic-camera-component.js +1 -1
- package/dist/domain/system-components/fit-entity-perspective-camera-component.js +1 -1
- package/dist/domain/system-components/fit-root-orthographic-camera-component.js +1 -1
- package/dist/domain/system-components/fit-root-perspective-camera-component.js +1 -1
- package/dist/domain/system-components/hotspot-drag-handle.d.ts +1 -1
- package/dist/domain/system-components/hotspot-drag-handle.js +1 -1
- package/dist/domain/system-components/index.d.ts +0 -68
- package/dist/domain/system-components/index.js +1 -1
- package/dist/domain/system-components/material-swatch.js +1 -1
- package/dist/domain/system-components/phoenix-entity-elements/admin-data-table-sidebar.d.ts +1 -1
- package/dist/domain/system-components/phoenix-layout-elements/admin-connected-resource-page.d.ts +1 -1
- package/dist/domain/system-components/phoenix-layout-elements/admin-data-table-actions.d.ts +1 -1
- package/dist/domain/system-components/phoenix-layout-elements/admin-data-table-page.d.ts +1 -1
- package/dist/domain/system-components/scene-entity-material-override.d.ts +1 -1
- package/dist/domain/system-components/scene-entity-object-material-override.d.ts +1 -1
- package/dist/domain/system-components/scene-entity-object-text-decal.d.ts +1 -1
- package/dist/domain/system-components/scene-entity-object-transimissive-material-override.d.ts +1 -1
- package/dist/domain/system-components/scene-entity-transimissive-material-override.d.ts +1 -1
- package/dist/domain/system-components/static-orthographic-camera-component.js +1 -1
- package/dist/domain/system-components/static-perspective-camera-component.js +1 -1
- package/dist/domain/utils/eval-cache.d.ts +1 -1
- package/dist/domain/utils/map-nested-object.d.ts +1 -1
- package/dist/domain/utils/memo-eval.d.ts +2 -2
- package/dist/editor/Editor.d.ts +7 -0
- package/dist/editor/Editor.js +1 -1
- package/dist/editor/domain-editors/DomainSpecContext.js +1 -1
- package/dist/editor/domain-editors/apply-spec-update.d.ts +10 -2
- package/dist/editor/domain-editors/apply-spec-update.js +1 -1
- package/dist/editor/domain-editors/use-implementation-files.d.ts +7 -16
- package/dist/editor/domain-editors/use-implementation-files.js +1 -1
- package/dist/editor/mocks/CreateMockYaml.js +2 -2
- package/dist/editor/mocks/EditorMockContext.d.ts +2 -0
- package/dist/editor/mocks/EditorMockContext.js +1 -1
- package/dist/editor/screenshot/ScreenshotHandler.js +1 -1
- package/dist/globals/index.d.ts +2 -0
- package/dist/i18n/locales/cs/messages.js +1 -1
- package/dist/i18n/locales/da/messages.js +1 -1
- package/dist/i18n/locales/de/messages.js +1 -1
- package/dist/i18n/locales/de-AT/messages.js +1 -1
- package/dist/i18n/locales/en/messages.js +1 -1
- package/dist/i18n/locales/en-AU/messages.js +1 -1
- package/dist/i18n/locales/en-GB/messages.js +1 -1
- package/dist/i18n/locales/es/messages.js +1 -1
- package/dist/i18n/locales/fr/messages.js +1 -1
- package/dist/i18n/locales/hu/messages.js +1 -1
- package/dist/i18n/locales/it/messages.js +1 -1
- package/dist/i18n/locales/nl/messages.js +1 -1
- package/dist/i18n/locales/pl/messages.js +1 -1
- package/dist/i18n/locales/pt/messages.js +1 -1
- package/dist/i18n/locales/ro/messages.js +1 -1
- package/dist/i18n/locales/sk/messages.js +1 -1
- package/dist/i18n/locales/tr/messages.js +1 -1
- package/dist/legacy/domain-cli/gen/configurator-builder.js +13 -12
- package/dist/legacy/domain-cli/gen/constant-expressions.js +1 -3
- package/dist/legacy/domain-cli/gen/defaults.js +1 -7
- package/dist/legacy/domain-cli/gen/implementation.js +7 -11
- package/dist/legacy/domain-cli/gen/invariants.js +7 -6
- package/dist/legacy/domain-cli/gen/relations.js +1 -1
- package/dist/legacy/domain-cli/utils/cache.d.ts +1 -1
- package/dist/legacy/domain-cli/utils/cache.js +1 -1
- package/dist/legacy/domain-spec/__generated/domain-schema.d.ts +176 -148
- package/dist/legacy/domain-spec/spec/domain-spec.js +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/claude-md.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/cursor-rules.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.js +13 -0
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/domain-index.js +17 -15
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/env-example.d.ts +1 -1
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/env-example.js +4 -0
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-admin.js +15 -14
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-configurator.js +14 -12
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-embedded-admin.js +14 -13
- package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-embedded-configurator.js +13 -11
- package/dist/phoenix/application-spec/application-spec-builder.d.ts +0 -2
- package/dist/phoenix/application-spec/application-spec-builder.js +1 -1
- package/dist/phoenix/application-spec/generate-application-spec-project.d.ts +0 -2
- package/dist/phoenix/application-spec/generate-application-spec-project.js +2 -2
- package/dist/phoenix/application-spec/generate-application-spec-story.js +2 -2
- package/dist/phoenix/application-spec/generate-project-files.d.ts +0 -2
- package/dist/phoenix/application-spec/project-files.js +1 -1
- package/dist/phoenix/bin.d.ts +2 -0
- package/dist/phoenix/bin.js +2 -0
- package/dist/phoenix/check-configs/built-in-eslint-config.d.ts +9 -0
- package/dist/phoenix/check-configs/built-in-eslint-config.js +1 -0
- package/dist/phoenix/check-configs/eslint-config.d.ts +10 -0
- package/dist/phoenix/check-configs/eslint-config.js +1 -0
- package/dist/phoenix/check-configs/index.d.ts +8 -0
- package/dist/phoenix/check-configs/index.js +1 -0
- package/dist/phoenix/check-configs/typescript-project.d.ts +5 -0
- package/dist/phoenix/check-configs/typescript-project.js +1 -0
- package/dist/phoenix/cli.d.ts +0 -1
- package/dist/phoenix/cli.js +1 -2
- package/dist/phoenix/commands/build.js +1 -1
- package/dist/phoenix/commands/check.js +1 -1
- package/dist/phoenix/commands/clean.js +1 -1
- package/dist/phoenix/commands/deploy.js +2 -2
- package/dist/phoenix/commands/deps/deps.d.ts +73 -0
- package/dist/phoenix/commands/deps/deps.js +3 -0
- package/dist/phoenix/commands/deps/index.d.ts +1 -0
- package/dist/phoenix/commands/deps/index.js +1 -0
- package/dist/phoenix/commands/deps/sdk-manifest.d.ts +12 -0
- package/dist/phoenix/commands/deps/sdk-manifest.js +1 -0
- package/dist/phoenix/commands/dev.js +5 -5
- package/dist/phoenix/commands/fix.js +1 -1
- package/dist/phoenix/commands/index.d.ts +1 -0
- package/dist/phoenix/commands/index.js +1 -1
- package/dist/phoenix/commands/init.d.ts +17 -5
- package/dist/phoenix/commands/init.js +1 -1
- package/dist/phoenix/commands/link.js +1 -1
- package/dist/phoenix/commands/prettier.js +1 -1
- package/dist/phoenix/commands/sync.js +1 -1
- package/dist/phoenix/commands/unlink.js +1 -1
- package/dist/phoenix/services/application-spec-project-watcher.d.ts +2 -2
- package/dist/phoenix/services/application-spec-project-watcher.js +3 -3
- package/dist/phoenix/services/lingui.d.ts +8 -0
- package/dist/phoenix/services/lingui.js +1 -1
- package/dist/phoenix/services/source-archive.d.ts +1 -0
- package/dist/phoenix/services/source-archive.js +1 -1
- package/dist/phoenix/services/swc.js +1 -1
- package/dist/phoenix/services/vite-config.js +1 -1
- package/dist/phoenix/utils/git/index.d.ts +1 -0
- package/dist/phoenix/utils/git/index.js +1 -0
- package/dist/phoenix/utils/git/is-inside-git-repository.d.ts +6 -0
- package/dist/phoenix/utils/git/is-inside-git-repository.js +1 -0
- package/dist/phoenix/utils/index.d.ts +5 -2
- package/dist/phoenix/utils/index.js +1 -1
- package/dist/phoenix/utils/node-version/index.d.ts +1 -0
- package/dist/phoenix/utils/node-version/index.js +1 -0
- package/dist/phoenix/utils/node-version/supported-node-version.d.ts +12 -0
- package/dist/phoenix/utils/node-version/supported-node-version.js +4 -0
- package/dist/phoenix/utils/package-info.d.ts +0 -9
- package/dist/phoenix/utils/package-info.js +1 -1
- package/dist/phoenix/utils/package-manager/index.d.ts +2 -0
- package/dist/phoenix/utils/package-manager/index.js +1 -0
- package/dist/phoenix/utils/package-manager/package-manager-commands.d.ts +19 -0
- package/dist/phoenix/utils/package-manager/package-manager-commands.js +1 -0
- package/dist/phoenix/utils/package-manager/pnpm-project.d.ts +9 -0
- package/dist/phoenix/utils/package-manager/pnpm-project.js +1 -0
- package/dist/phoenix/utils/run-command.d.ts +11 -0
- package/dist/phoenix/utils/run-command.js +1 -1
- package/dist/phoenix/utils/sdk-tool/index.d.ts +1 -0
- package/dist/phoenix/utils/sdk-tool/index.js +1 -0
- package/dist/phoenix/utils/sdk-tool/resolve-sdk-tool-bin.d.ts +15 -0
- package/dist/phoenix/utils/sdk-tool/resolve-sdk-tool-bin.js +1 -0
- package/dist/react-sdk/application-runtime/common-services/analytics/analytics-service.d.ts +1 -1
- package/dist/react-sdk/application-runtime/common-services/analytics/frontend-analytics.d.ts +52 -0
- package/dist/react-sdk/application-runtime/common-services/analytics/frontend-analytics.js +1 -0
- package/dist/react-sdk/application-runtime/common-services/analytics/index.d.ts +1 -0
- package/dist/react-sdk/application-runtime/common-services/analytics/index.js +1 -1
- package/dist/react-sdk/application-runtime/common-services/initialize.d.ts +2 -2
- package/dist/react-sdk/application-runtime/hooks/index.d.ts +2 -0
- package/dist/react-sdk/application-runtime/hooks/index.js +1 -1
- package/dist/react-sdk/application-runtime/hooks/loading/index.d.ts +5 -0
- package/dist/react-sdk/application-runtime/hooks/loading/index.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/loading/loader-host-context.d.ts +11 -0
- package/dist/react-sdk/application-runtime/hooks/loading/loader-host-context.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/loading/use-is-awaiting-loading-phase.d.ts +8 -0
- package/dist/react-sdk/application-runtime/hooks/loading/use-is-awaiting-loading-phase.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/loading/use-is-loader-hosted.d.ts +7 -0
- package/dist/react-sdk/application-runtime/hooks/loading/use-is-loader-hosted.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/loading/use-loading-actions.d.ts +9 -0
- package/dist/react-sdk/application-runtime/hooks/loading/use-loading-actions.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/loading/use-loading-application.d.ts +24 -0
- package/dist/react-sdk/application-runtime/hooks/loading/use-loading-application.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/index.d.ts +3 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/index.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-actions.d.ts +9 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-actions.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-application.d.ts +26 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-application.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-state.d.ts +10 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-state.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-store.d.ts +24 -0
- package/dist/react-sdk/application-runtime/hooks/responsive/use-responsive-store.js +1 -0
- package/dist/react-sdk/application-runtime/hooks/use-access-service.d.ts +7 -0
- package/dist/react-sdk/application-runtime/hooks/use-access-service.js +1 -1
- package/dist/react-sdk/application-runtime/hooks/use-phoenix-element-slots.d.ts +2 -1
- package/dist/react-sdk/application-runtime/hooks/use-web-component-readonly-sync.d.ts +4 -11
- package/dist/react-sdk/application-runtime/hooks/use-web-component-readonly-sync.js +1 -1
- package/dist/react-sdk/application-runtime/hooks/use-web-component.d.ts +5 -0
- package/dist/react-sdk/application-runtime/hooks/use-web-component.js +1 -1
- package/dist/react-sdk/application-runtime/host-properties/actions.d.ts +15 -0
- package/dist/react-sdk/application-runtime/host-properties/actions.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/connect-host-properties.d.ts +30 -0
- package/dist/react-sdk/application-runtime/host-properties/connect-host-properties.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/domain-host-properties.d.ts +7 -0
- package/dist/react-sdk/application-runtime/host-properties/domain-host-properties.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/host-property-descriptors.d.ts +21 -0
- package/dist/react-sdk/application-runtime/host-properties/host-property-descriptors.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/host-property-scopes.d.ts +42 -0
- package/dist/react-sdk/application-runtime/host-properties/host-property-scopes.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/index.d.ts +8 -0
- package/dist/react-sdk/application-runtime/host-properties/index.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/initialize.d.ts +7 -0
- package/dist/react-sdk/application-runtime/host-properties/initialize.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/selectors.d.ts +54 -0
- package/dist/react-sdk/application-runtime/host-properties/selectors.js +1 -0
- package/dist/react-sdk/application-runtime/host-properties/types.d.ts +18 -0
- package/dist/react-sdk/application-runtime/host-properties/types.js +1 -0
- package/dist/react-sdk/application-runtime/index.d.ts +3 -0
- package/dist/react-sdk/application-runtime/integration/initialize.d.ts +7 -6
- package/dist/react-sdk/application-runtime/integration/initialize.js +1 -1
- package/dist/react-sdk/application-runtime/integration/selectors.d.ts +11 -2
- package/dist/react-sdk/application-runtime/integration/selectors.js +1 -1
- package/dist/react-sdk/application-runtime/integration/types.d.ts +5 -2
- package/dist/react-sdk/application-runtime/loading/actions.d.ts +45 -0
- package/dist/react-sdk/application-runtime/loading/actions.js +1 -0
- package/dist/react-sdk/application-runtime/loading/index.d.ts +4 -0
- package/dist/react-sdk/application-runtime/loading/index.js +1 -0
- package/dist/react-sdk/application-runtime/loading/initialize.d.ts +3 -0
- package/dist/react-sdk/application-runtime/loading/initialize.js +1 -0
- package/dist/react-sdk/application-runtime/loading/selectors.d.ts +18 -0
- package/dist/react-sdk/application-runtime/loading/selectors.js +1 -0
- package/dist/react-sdk/application-runtime/loading/types.d.ts +59 -0
- package/dist/react-sdk/application-runtime/loading/types.js +1 -0
- package/dist/react-sdk/application-runtime/mocks/create-mock-application-runtime.js +1 -1
- package/dist/react-sdk/application-runtime/provider/ApplicationRuntime.d.ts +6 -0
- package/dist/react-sdk/application-runtime/provider/ApplicationRuntime.js +1 -1
- package/dist/react-sdk/application-runtime/provider/ApplicationType.d.ts +7 -0
- package/dist/react-sdk/application-runtime/provider/ApplicationType.js +1 -1
- package/dist/react-sdk/application-runtime/responsive/actions.d.ts +47 -0
- package/dist/react-sdk/application-runtime/responsive/actions.js +1 -0
- package/dist/react-sdk/application-runtime/responsive/index.d.ts +4 -0
- package/dist/react-sdk/application-runtime/responsive/index.js +1 -0
- package/dist/react-sdk/application-runtime/responsive/initialize.d.ts +3 -0
- package/dist/react-sdk/application-runtime/responsive/initialize.js +1 -0
- package/dist/react-sdk/application-runtime/responsive/selectors.d.ts +20 -0
- package/dist/react-sdk/application-runtime/responsive/selectors.js +1 -0
- package/dist/react-sdk/application-runtime/responsive/types.d.ts +104 -0
- package/dist/react-sdk/application-runtime/responsive/types.js +1 -0
- package/dist/react-sdk/application-runtime/routing/access-service.d.ts +1 -0
- package/dist/react-sdk/application-runtime/routing/routing-service.d.ts +34 -2
- package/dist/react-sdk/application-runtime/routing/routing-service.js +1 -1
- package/dist/react-sdk/application-runtime/runtime/create-application-runtime.d.ts +366 -46
- package/dist/react-sdk/application-runtime/runtime/create-application-runtime.js +1 -1
- package/dist/react-sdk/domain-application-context/BackendDataProvider.js +1 -1
- package/dist/react-sdk/domain-application-context/DomainLifecycleBoundary.js +1 -1
- package/dist/react-sdk/domain-application-context/FrontendDataProvider.js +1 -1
- package/dist/react-sdk/domain-application-context/PublishedDataProvider.js +1 -1
- package/dist/react-sdk/domain-context/common-hooks/index.d.ts +9 -0
- package/dist/react-sdk/domain-context/common-hooks/index.js +1 -0
- package/dist/react-sdk/domain-context/common-hooks/use-configuration-save-state.d.ts +23 -0
- package/dist/react-sdk/domain-context/common-hooks/use-configuration-save-state.js +1 -0
- package/dist/react-sdk/domain-context/common-hooks/use-configurator-messages.d.ts +41 -0
- package/dist/react-sdk/domain-context/common-hooks/use-configurator-messages.js +1 -0
- package/dist/react-sdk/domain-context/common-hooks/use-domain-context.d.ts +10 -9
- 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/index.d.ts +9 -1
- package/dist/react-sdk/domain-context/index.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/generate-share-link-hooks.d.ts +8 -0
- package/dist/react-sdk/domain-context/system-hooks/generate-share-link-hooks.js +1 -0
- package/dist/react-sdk/domain-context/system-hooks/hover-hooks.js +1 -1
- package/dist/react-sdk/domain-context/system-hooks/locale-switch-hooks.d.ts +24 -0
- package/dist/react-sdk/domain-context/system-hooks/locale-switch-hooks.js +1 -0
- package/dist/react-sdk/domain-context/system-hooks/navigation-hooks.d.ts +11 -0
- 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/share-links-hooks.d.ts +32 -0
- package/dist/react-sdk/domain-context/system-hooks/share-links-hooks.js +1 -0
- 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/domain-context/system-hooks/undo-redo-hooks.d.ts +20 -0
- package/dist/react-sdk/domain-context/system-hooks/undo-redo-hooks.js +1 -0
- package/dist/react-sdk/domain-controls/hooks/use-angle-dimension.d.ts +1 -1
- 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 +6 -0
- package/dist/react-sdk/handlers/index.js +1 -1
- package/dist/react-sdk/handlers/use-finalize-action.d.ts +32 -0
- package/dist/react-sdk/handlers/use-finalize-action.js +1 -0
- 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 +2 -0
- package/dist/react-sdk/index.js +1 -1
- package/dist/react-sdk/loading-state/LoaderHost.d.ts +20 -0
- package/dist/react-sdk/loading-state/LoaderHost.js +1 -0
- package/dist/react-sdk/loading-state/built-in-loading-phase.d.ts +9 -0
- package/dist/react-sdk/loading-state/built-in-loading-phase.js +1 -0
- package/dist/react-sdk/loading-state/create-app-loader.d.ts +18 -0
- package/dist/react-sdk/loading-state/create-app-loader.js +1 -0
- package/dist/react-sdk/loading-state/index.d.ts +4 -2
- package/dist/react-sdk/loading-state/index.js +1 -1
- package/dist/react-sdk/loading-state/loading-state-hooks.d.ts +22 -0
- package/dist/react-sdk/loading-state/loading-state-hooks.js +1 -0
- package/dist/react-sdk/page-elements/ConfigurationNotFoundPagePropsProvider.d.ts +17 -0
- package/dist/react-sdk/page-elements/ConfigurationNotFoundPagePropsProvider.js +1 -0
- package/dist/react-sdk/page-elements/ConfiguratorErrorPagePropsProvider.d.ts +17 -0
- package/dist/react-sdk/page-elements/ConfiguratorErrorPagePropsProvider.js +1 -0
- package/dist/react-sdk/page-elements/configurator-error-page-props.d.ts +20 -0
- package/dist/react-sdk/page-elements/configurator-error-page-props.js +1 -0
- package/dist/react-sdk/page-elements/create-configuration-not-found-page.d.ts +15 -0
- package/dist/react-sdk/page-elements/create-configuration-not-found-page.js +1 -0
- package/dist/react-sdk/page-elements/create-configurator-error-page.d.ts +14 -0
- package/dist/react-sdk/page-elements/create-configurator-error-page.js +1 -0
- package/dist/react-sdk/page-elements/create-preset-ui.d.ts +9 -0
- package/dist/react-sdk/page-elements/create-preset-ui.js +1 -0
- package/dist/react-sdk/page-elements/index.d.ts +6 -0
- package/dist/react-sdk/page-elements/index.js +1 -0
- package/dist/react-sdk/scene-control-elements/AddEntityFabElement.d.ts +36 -0
- package/dist/react-sdk/scene-control-elements/AddEntityFabElement.js +1 -0
- package/dist/react-sdk/scene-control-elements/DeleteEntityFabElement.d.ts +44 -0
- package/dist/react-sdk/scene-control-elements/DeleteEntityFabElement.js +1 -0
- package/dist/react-sdk/scene-control-elements/NavigationHotspotElement.d.ts +38 -0
- package/dist/react-sdk/scene-control-elements/NavigationHotspotElement.js +1 -0
- package/dist/react-sdk/scene-control-elements/SelectionHotspotElement.d.ts +30 -0
- package/dist/react-sdk/scene-control-elements/SelectionHotspotElement.js +1 -0
- package/dist/react-sdk/scene-control-elements/ToolMenuFabElement.d.ts +30 -0
- package/dist/react-sdk/scene-control-elements/ToolMenuFabElement.js +1 -0
- package/dist/react-sdk/scene-control-elements/create-add-entity-fab.d.ts +9 -0
- package/dist/react-sdk/scene-control-elements/create-add-entity-fab.js +1 -0
- package/dist/react-sdk/scene-control-elements/create-delete-entity-fab.d.ts +9 -0
- package/dist/react-sdk/scene-control-elements/create-delete-entity-fab.js +1 -0
- package/dist/react-sdk/scene-control-elements/create-navigation-hotspot.d.ts +10 -0
- package/dist/react-sdk/scene-control-elements/create-navigation-hotspot.js +1 -0
- package/dist/react-sdk/scene-control-elements/create-selection-hotspot.d.ts +9 -0
- package/dist/react-sdk/scene-control-elements/create-selection-hotspot.js +1 -0
- package/dist/react-sdk/scene-control-elements/create-tool-menu-fab.d.ts +9 -0
- package/dist/react-sdk/scene-control-elements/create-tool-menu-fab.js +1 -0
- package/dist/react-sdk/scene-control-elements/index.d.ts +11 -0
- package/dist/react-sdk/scene-control-elements/index.js +1 -0
- package/dist/react-sdk/scene-control-elements/scene-control-tooltip-visibility.d.ts +5 -0
- package/dist/react-sdk/scene-control-elements/scene-control-tooltip-visibility.js +1 -0
- package/dist/react-sdk/session/NotAuthenticatedPage.d.ts +4 -0
- package/dist/react-sdk/session/NotAuthenticatedPage.js +1 -1
- package/dist/react-sdk/session/SessionServiceProvider.js +1 -1
- package/dist/react-sdk/web-component/create-web-component.js +1 -1
- package/dist/react-sdk/web-component/god-mode/god-mode-registry.d.ts +30 -0
- package/dist/react-sdk/web-component/god-mode/god-mode-registry.js +1 -0
- package/dist/react-sdk/web-component/god-mode/index.d.ts +1 -0
- package/dist/react-sdk/web-component/god-mode/index.js +1 -0
- package/dist/ui-sdk/icons/__generated/Lightbulb.d.ts +3 -0
- package/dist/ui-sdk/icons/__generated/Lightbulb.js +1 -0
- package/dist/ui-sdk/icons/__generated/XCircle.d.ts +3 -0
- package/dist/ui-sdk/icons/__generated/XCircle.js +1 -0
- package/dist/ui-sdk/icons/__generated/index.d.ts +4 -0
- package/dist/ui-sdk/icons/__generated/index.js +1 -1
- package/dist/ui-sdk/icons/icons-registry.js +1 -1
- 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 +59 -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/dist/ui-sdk/theme/types.d.ts +5 -1
- package/dist/webhook/builders/common.d.ts +5 -0
- package/dist/webhook/builders/webhook-builder.js +1 -1
- package/package.json +58 -36
- package/schema/domain-spec.json +1341 -217
- package/dist/application-spec/core/eval-to-constant.d.ts +0 -14
- package/dist/application-spec/generate-application/code/templates/env-ci.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/env-ci.js +0 -25
- package/dist/application-spec/generate-application/code/templates/env-example.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/env-example.js +0 -90
- package/dist/application-spec/generate-application/code/templates/nvmrc.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/nvmrc.js +0 -2
- package/dist/application-spec/generate-application/code/templates/package-json.d.ts +0 -10
- package/dist/application-spec/generate-application/code/templates/package-json.js +0 -1
- package/dist/application-spec/generate-application/code/templates/prettier-ignore.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/prettier-ignore.js +0 -6
- package/dist/application-spec/generate-application/code/templates/private-assets-readme.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/private-assets-readme.js +0 -24
- package/dist/application-spec/generate-application/code/templates/public-assets-readme.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/public-assets-readme.js +0 -24
- package/dist/application-spec/generate-application/code/templates/readme.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/readme.js +0 -23
- package/dist/application-spec/generate-application/code/templates/skills/agents-md.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/skills/agents-md.js +0 -2
- package/dist/application-spec/generate-application/code/templates/webhook-implementation.d.ts +0 -1
- package/dist/application-spec/generate-application/code/templates/webhook-implementation.js +0 -7
- package/dist/configurator/common/frontend-analytics.d.ts +0 -46
- package/dist/configurator/common/frontend-analytics.js +0 -1
- package/dist/configurator/ui/phoenix-layout-elements/language/use-locale-options.d.ts +0 -4
- package/dist/configurator/ui/phoenix-layout-elements/language/use-locale-options.js +0 -1
- package/dist/configurator/ui/phoenix-layout-elements/measurements/use-measurements-visibility.d.ts +0 -4
- package/dist/configurator/ui/phoenix-layout-elements/measurements/use-measurements-visibility.js +0 -1
- package/dist/domain/actions/action-factory.d.ts +0 -10
- package/dist/domain/actions/prepare-action-implementations.d.ts +0 -15
- package/dist/domain/actions/prepare-action-implementations.js +0 -5
- package/dist/domain/application/element-registration.d.ts +0 -110
- package/dist/domain/application/element-registration.js +0 -1
- package/dist/domain/application/handler-registration.d.ts +0 -34
- package/dist/domain/application/handler-registration.js +0 -1
- package/dist/domain/application/resolve-imported-transformers.d.ts +0 -23
- package/dist/domain/application/resolve-imported-transformers.js +0 -1
- package/dist/domain/application/resource-builder.d.ts +0 -13
- package/dist/domain/application/resource-builder.js +0 -1
- package/dist/domain/core/action.d.ts +0 -66
- package/dist/domain/core/expression/memoize-expression.d.ts +0 -17
- package/dist/domain/core/expression/prepare-domain-expressions.d.ts +0 -16
- package/dist/domain/domain-context/recipe-system/recipes.d.ts +0 -55
- package/dist/domain/domain-context/recipe-system/recipes.js +0 -1
- package/dist/domain/project-modules/index.d.ts +0 -1
- package/dist/domain/project-modules/index.js +0 -1
- package/dist/domain/project-modules/project-modules.d.ts +0 -41
- package/dist/domain/project-modules/project-modules.js +0 -1
- package/dist/domain/relations/all-of-type-relation.d.ts +0 -30
- package/dist/domain/relations/all-of-type-relation.js +0 -1
- package/dist/domain/relations/component-factories.d.ts +0 -14
- package/dist/domain/relations/component-factories.js +0 -1
- package/dist/domain/relations/composed-of.d.ts +0 -12
- package/dist/domain/relations/composed-of.js +0 -1
- package/dist/domain/relations/direct-relation.d.ts +0 -27
- package/dist/domain/relations/indirect-relation.d.ts +0 -78
- package/dist/domain/relations/indirect-relation.js +0 -1
- package/dist/domain/relations/reference-component.d.ts +0 -16
- package/dist/domain/relations/reference-component.js +0 -1
- package/dist/domain/relations/reference-field.d.ts +0 -7
- package/dist/domain/relations/relation-factory.d.ts +0 -17
- package/dist/domain/relations/relation-factory.js +0 -1
- package/dist/domain/relations/relation-implementation.d.ts +0 -5
- package/dist/domain/system-components/attribute-switch.d.ts +0 -8
- package/dist/domain/system-components/attribute-switch.js +0 -1
- package/dist/domain/system-components/canvas-config.d.ts +0 -87
- package/dist/domain/system-components/child-entity-recipe-hierarchy.d.ts +0 -21
- package/dist/domain/system-components/child-entity-recipe-hierarchy.js +0 -1
- package/dist/domain/system-components/client-side-image.d.ts +0 -14
- package/dist/domain/system-components/client-side-image.js +0 -1
- package/dist/domain/system-components/common/angle-indicator-schema.d.ts +0 -292
- package/dist/domain/system-components/common/angle-indicator-schema.js +0 -1
- package/dist/domain/system-components/common/dimension-indicator-schema.d.ts +0 -607
- package/dist/domain/system-components/custom-header.d.ts +0 -11
- package/dist/domain/system-components/custom-header.js +0 -1
- package/dist/domain/system-components/custom-price-footer.js +0 -1
- package/dist/domain/system-components/custom-sidebar-footer.d.ts +0 -11
- package/dist/domain/system-components/custom-sidebar-footer.js +0 -1
- package/dist/domain/system-components/editable-angle-indicator.d.ts +0 -38
- package/dist/domain/system-components/editable-angle-indicator.js +0 -1
- package/dist/domain/system-components/editable-distance-dimension-indicator.d.ts +0 -42
- package/dist/domain/system-components/editable-distance-dimension-indicator.js +0 -1
- package/dist/domain/system-components/editable-offset-indicator.d.ts +0 -41
- package/dist/domain/system-components/editable-offset-indicator.js +0 -1
- package/dist/domain/system-components/editable-size-indicator.d.ts +0 -38
- package/dist/domain/system-components/editable-size-indicator.js +0 -1
- package/dist/domain/system-components/entity-header.d.ts +0 -22
- package/dist/domain/system-components/entity-header.js +0 -1
- package/dist/domain/system-components/finalize-component.js +0 -1
- package/dist/domain/system-components/fit-selection-orthographic-camera-component.js +0 -1
- package/dist/domain/system-components/fit-selection-perspective-camera-component.js +0 -1
- package/dist/domain/system-components/horizontal-tabs.d.ts +0 -28
- package/dist/domain/system-components/list-selector-dimension-indicator.d.ts +0 -30
- package/dist/domain/system-components/list-selector-dimension-indicator.js +0 -1
- package/dist/domain/system-components/navigation-steps.d.ts +0 -19
- package/dist/domain/system-components/offset-indicator.js +0 -1
- package/dist/domain/system-components/property-page.d.ts +0 -16
- package/dist/domain/system-components/property-page.js +0 -1
- package/dist/domain/system-components/replace-entity-recipe-hierarchy.d.ts +0 -21
- package/dist/domain/system-components/replace-entity-recipe-hierarchy.js +0 -1
- package/dist/domain/system-components/rotation-handle.d.ts +0 -53
- package/dist/domain/system-components/save-template-thumbnail.d.ts +0 -14
- package/dist/domain/system-components/save-template-thumbnail.js +0 -1
- package/dist/domain/system-components/scene-entity-add-fab.d.ts +0 -32
- package/dist/domain/system-components/scene-entity-add-fab.js +0 -1
- package/dist/domain/system-components/scene-entity-animation.js +0 -1
- package/dist/domain/system-components/scene-entity-delete-fab.d.ts +0 -19
- package/dist/domain/system-components/scene-entity-delete-fab.js +0 -1
- package/dist/domain/system-components/scene-entity-html-hotspot.d.ts +0 -27
- package/dist/domain/system-components/scene-entity-html-hotspot.js +0 -1
- package/dist/domain/system-components/scene-entity-navigation-hotspot.d.ts +0 -20
- package/dist/domain/system-components/scene-entity-navigation-hotspot.js +0 -1
- package/dist/domain/system-components/scene-entity-object-html-hotspot.d.ts +0 -31
- package/dist/domain/system-components/scene-entity-object-html-hotspot.js +0 -1
- package/dist/domain/system-components/scene-entity-select-hotspot.d.ts +0 -5
- package/dist/domain/system-components/scene-entity-select-hotspot.js +0 -1
- package/dist/domain/system-components/sketching-edit.d.ts +0 -28
- package/dist/domain/system-components/sketching-edit.js +0 -1
- package/dist/domain/system-components/summary-component.js +0 -1
- package/dist/domain/system-components/tool-menu-fab.d.ts +0 -40
- package/dist/domain/system-components/tool-menu-fab.js +0 -1
- package/dist/domain/system-components/toolbar.d.ts +0 -13
- package/dist/domain/system-components/toolbar.js +0 -1
- package/dist/phoenix/commands/generate-project-files.d.ts +0 -6
- package/dist/phoenix/commands/generate-project-files.js +0 -1
- package/dist/react-sdk/application-runtime/runtime/god-mode.d.ts +0 -19
- package/dist/react-sdk/application-runtime/runtime/god-mode.js +0 -1
- package/dist/react-sdk/loading-state/LoadingStateProvider.d.ts +0 -31
- package/dist/react-sdk/loading-state/LoadingStateProvider.js +0 -1
- package/dist/react-sdk/loading-state/loading-state-manager.d.ts +0 -32
- package/dist/react-sdk/loading-state/loading-state-manager.js +0 -1
- /package/dist/{domain/core/action.js → application-spec/core/expressions/relation-expression-params.js} +0 -0
- /package/dist/application-spec/{core → enhanced-json}/eval-code-uses-self.d.ts +0 -0
- /package/dist/application-spec/{core → enhanced-json}/eval-code-uses-self.js +0 -0
- /package/dist/application-spec/{core → enhanced-json}/eval-to-constant.js +0 -0
- /package/dist/{domain/relations/reference-field.js → application-spec/generate-application/runtime/spec-code/spec-code-module.js} +0 -0
- /package/dist/{domain/relations/relation-implementation.js → application-spec/parse-application-files/spec/expressions/expression-read.js} +0 -0
- /package/dist/domain/{actions → core/action}/action-factory.js +0 -0
- /package/dist/domain/{relations → core/relation}/direct-relation.js +0 -0
- /package/dist/domain/{core/expression → domain-runtime/spec}/memoize-expression.js +0 -0
- /package/dist/domain/{core/expression → domain-runtime/spec}/prepare-domain-expressions.js +0 -0
- /package/dist/domain/{system-components → extensions}/angle-indicator.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/angle-indicator.js +0 -0
- /package/dist/domain/{system-components → extensions}/autorotate-camera-component.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/autorotate-camera-component.js +0 -0
- /package/dist/domain/{system-components → extensions}/basic-sidebar-footer.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/basic-sidebar-footer.js +0 -0
- /package/dist/domain/{system-components → extensions}/camera-based-hidden-entity-object.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/camera-based-hidden-entity-object.js +0 -0
- /package/dist/domain/{system-components → extensions}/camera-based-hidden-entity.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/camera-based-hidden-entity.js +0 -0
- /package/dist/domain/{system-components → extensions}/canvas-config.js +0 -0
- /package/dist/domain/{system-components → extensions}/canvas-custom-environment.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/canvas-custom-environment.js +0 -0
- /package/dist/domain/{system-components → extensions}/canvas-padding.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/canvas-padding.js +0 -0
- /package/dist/domain/{system-components → extensions}/canvas-preset-environment.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/canvas-preset-environment.js +0 -0
- /package/dist/domain/{system-components → extensions}/color-value.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/color-value.js +0 -0
- /package/dist/domain/{system-components → extensions}/common/dimension-indicator-schema.js +0 -0
- /package/dist/domain/{system-components → extensions}/common/dimension-indicators.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/common/dimension-indicators.js +0 -0
- /package/dist/domain/{system-components → extensions}/common/editable-indicator-button-variant.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/common/editable-indicator-button-variant.js +0 -0
- /package/dist/domain/{system-components → extensions}/custom-price-footer.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/dimension-indicator.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/dimension-indicator.js +0 -0
- /package/dist/domain/{system-components → extensions}/exit-tool-mode-fab.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/exit-tool-mode-fab.js +0 -0
- /package/dist/domain/{system-components → extensions}/finalize-action.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/finalize-action.js +0 -0
- /package/dist/domain/{system-components → extensions}/finalize-component.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/fit-selection-orthographic-camera-component.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/fit-selection-perspective-camera-component.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/fixed-header.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/fixed-header.js +0 -0
- /package/dist/domain/{system-components → extensions}/floating-header.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/floating-header.js +0 -0
- /package/dist/domain/{system-components → extensions}/hidden-header.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/hidden-header.js +0 -0
- /package/dist/domain/{system-components → extensions}/hidden-sidebar-footer.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/hidden-sidebar-footer.js +0 -0
- /package/dist/domain/{system-components → extensions}/horizontal-tabs.js +0 -0
- /package/dist/domain/{system-components → extensions}/multilingual-text-value.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/multilingual-text-value.js +0 -0
- /package/dist/domain/{system-components → extensions}/navigation-steps.js +0 -0
- /package/dist/domain/{system-components → extensions}/offset-anchors.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/offset-anchors.js +0 -0
- /package/dist/domain/{system-components → extensions}/offset-indicator.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/offset.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/offset.js +0 -0
- /package/dist/domain/{system-components → extensions}/other-action.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/other-action.js +0 -0
- /package/dist/domain/{system-components → extensions}/read-sidebar-footer.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/read-sidebar-footer.js +0 -0
- /package/dist/domain/{system-components → extensions}/rotatable.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/rotatable.js +0 -0
- /package/dist/domain/{system-components → extensions}/rotation-handle.js +0 -0
- /package/dist/domain/{system-components → extensions}/scene-entity-animation.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/scene-entity-control.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/scene-entity-control.js +0 -0
- /package/dist/domain/{system-components → extensions}/scene-entity-floor-object.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/scene-entity-floor-object.js +0 -0
- /package/dist/domain/{system-components → extensions}/scene-entity-wall-object.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/scene-entity-wall-object.js +0 -0
- /package/dist/domain/{system-components → extensions}/selectable-component.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/selectable-component.js +0 -0
- /package/dist/domain/{system-components → extensions}/sidebar-tabs.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/sidebar-tabs.js +0 -0
- /package/dist/domain/{system-components → extensions}/simple-summary.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/simple-summary.js +0 -0
- /package/dist/domain/{system-components → extensions}/size.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/size.js +0 -0
- /package/dist/domain/{system-components → extensions}/sketching-draw.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/sketching-draw.js +0 -0
- /package/dist/domain/{system-components → extensions}/summary-component.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/text-value.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/text-value.js +0 -0
- /package/dist/domain/{system-components → extensions}/tool-controls.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/tool-controls.js +0 -0
- /package/dist/domain/{system-components → extensions}/universal-sidebar-footer.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/universal-sidebar-footer.js +0 -0
- /package/dist/domain/{system-components → extensions}/view-controls.d.ts +0 -0
- /package/dist/domain/{system-components → extensions}/view-controls.js +0 -0
|
@@ -0,0 +1,3516 @@
|
|
|
1
|
+
# Ardent Compressor
|
|
2
|
+
|
|
3
|
+
Quote an engineered product from a form, with no 3D model at all. An industrial compressor maker, Ardent Air, lets plant engineers size a rotary screw compressor and ask for a firm quote. The engineer works through five steps: the air demand, the air treatment, the plant room, service and delivery, and a review. Questions appear only when an earlier answer makes them relevant, conflicts between choices are flagged at the field that can fix them, and a price breakdown with every line, the subtotal and an express-delivery surcharge follows each change. **Request quote** sends the configuration to an application engineer and shows the answer. The configurator mounts no canvas: the spec holds the product, the prices, the rules and the quote action, and the layout is an ordinary React form.
|
|
4
|
+
|
|
5
|
+
- [Try it](#try-it)
|
|
6
|
+
- [What the example demonstrates](#what-the-example-demonstrates)
|
|
7
|
+
- [A configurator with no scene](#a-configurator-with-no-scene)
|
|
8
|
+
- [Steps from the spec](#steps-from-the-spec)
|
|
9
|
+
- [Questions that depend on earlier answers](#questions-that-depend-on-earlier-answers)
|
|
10
|
+
- [Options that follow the configuration](#options-that-follow-the-configuration)
|
|
11
|
+
- [Validation from invariants](#validation-from-invariants)
|
|
12
|
+
- [A live price breakdown](#a-live-price-breakdown)
|
|
13
|
+
- [Request a quote](#request-a-quote)
|
|
14
|
+
- [On a phone](#on-a-phone)
|
|
15
|
+
- [Test cases](#test-cases)
|
|
16
|
+
- [What this layout leaves out](#what-this-layout-leaves-out)
|
|
17
|
+
- [Related pages](#related-pages)
|
|
18
|
+
- [Complete Implementation](#complete-implementation)
|
|
19
|
+
|
|
20
|
+
## Try it
|
|
21
|
+
|
|
22
|
+
The phone version is under [On a phone](#on-a-phone).
|
|
23
|
+
|
|
24
|
+
## What the example demonstrates
|
|
25
|
+
|
|
26
|
+
[Custom Layout](?path=/docs/layouts-custom-layout--docs) explains how a project layout works. This page uses the same element for a product that has no model: the configuration backend, with its spec, rules, storage and finalize actions, runs a form.
|
|
27
|
+
|
|
28
|
+
| Piece | Where |
|
|
29
|
+
| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| A `ConfiguratorLayout` that mounts no `LayoutSlots.SharedCanvas` | [A configurator with no scene](#a-configurator-with-no-scene) |
|
|
31
|
+
| `navigationSteps` with `phx.frontend.useStepNavigation()` | `StepHeader`, [Steps from the spec](#steps-from-the-spec) |
|
|
32
|
+
| `$filter.active` with `$eval`, and `phx.frontend.useComponent()` to follow it | `ChoiceField`, [Questions that depend on earlier answers](#questions-that-depend-on-earlier-answers) |
|
|
33
|
+
| `attributeSelection` options read from an expression | [Options that follow the configuration](#options-that-follow-the-configuration) |
|
|
34
|
+
| `warning` invariants shown at the field, read with `phx.frontend.useFinalizeViolations()` | `useWarnings`, `FieldShell`, [Validation from invariants](#validation-from-invariants) |
|
|
35
|
+
| Typed expressions declared in `expressions:` and read with `phx.frontend.useRootEntityState()` | `PriceBreakdown`, [A live price breakdown](#a-live-price-breakdown) |
|
|
36
|
+
| `genericDimension`, `attributeSwitch` and `textValue` controls in the layout's own inputs | `NumberField`, `SwitchField`, `TextInputField` |
|
|
37
|
+
| `phx.frontend.useFinalizeActions()` with a project handler that stands in for a webhook | `QuoteAction`, `request-quote.ts`, [Request a quote](#request-a-quote) |
|
|
38
|
+
| `phx.frontend.useUndoRedo()` in the brand bar | `DesktopPage` |
|
|
39
|
+
|
|
40
|
+
## A configurator with no scene
|
|
41
|
+
|
|
42
|
+
`src/elements/ConfiguratorLayout.tsx` mounts the services and a page, and never `LayoutSlots.SharedCanvas`. Without a canvas the app loader leaves as soon as the configuration is up, nothing waits for a scene, and no model, environment map or texture is downloaded, even when the spec names one; see [Configurator Interface → App Loader](?path=/docs/phoenix-configurator-interface--docs#app-loader). The spec needs no camera and no scene component.
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { Box } from '@mui/material'
|
|
46
|
+
|
|
47
|
+
import { LayoutSlots } from '@salsita-ai/sdk/configurator'
|
|
48
|
+
|
|
49
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
50
|
+
import { ARDENT } from '../_form/index.js'
|
|
51
|
+
import { DesktopPage } from '../_shared/DesktopPage.js'
|
|
52
|
+
import { PhonePage } from '../_shared/PhonePage.js'
|
|
53
|
+
|
|
54
|
+
// A quote builder with no scene: the layout mounts the services and a page, never `LayoutSlots.SharedCanvas`, so the
|
|
55
|
+
// configurator loads no 3D scene and the app loader does not wait for one. Desktop or phone follows the width of the
|
|
56
|
+
// configurator's container.
|
|
57
|
+
const ConfiguratorLayout = () => {
|
|
58
|
+
// `null` until the container has been measured
|
|
59
|
+
const viewports = phx.frontend.useActiveViewports()
|
|
60
|
+
const isDesktop = viewports?.includes('desktop') ?? false
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Box component="div" sx={{ position: 'absolute', inset: 0, overflow: 'clip', bgcolor: ARDENT.page }}>
|
|
64
|
+
<LayoutSlots.Services />
|
|
65
|
+
{viewports !== null && (isDesktop ? <DesktopPage /> : <PhonePage />)}
|
|
66
|
+
</Box>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', ConfiguratorLayout)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`phx.frontend.useActiveViewports()` measures the configurator's container, not the window, so the **Phone** story's 390-pixel frame shows the phone page. Finalizing works the same without a canvas, as long as the spec asks for no client-side images: an image capture waits for a canvas and fails after 10 seconds without one.
|
|
74
|
+
|
|
75
|
+
## Steps from the spec
|
|
76
|
+
|
|
77
|
+
The steps are the spec's `navigationSteps`. Each step names the navigation state it enters; the last one also holds `requested`, the state the quote handler enters, so the review stays the active step after the request.
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
navigationStates:
|
|
81
|
+
- duty
|
|
82
|
+
- treatment
|
|
83
|
+
- installation
|
|
84
|
+
- service
|
|
85
|
+
- review
|
|
86
|
+
- requested
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
components:
|
|
91
|
+
Compressor:
|
|
92
|
+
navigationSteps:
|
|
93
|
+
main:
|
|
94
|
+
steps:
|
|
95
|
+
- label: Duty
|
|
96
|
+
navigationStates:
|
|
97
|
+
- duty
|
|
98
|
+
- label: Air treatment
|
|
99
|
+
navigationStates:
|
|
100
|
+
- treatment
|
|
101
|
+
- label: Installation
|
|
102
|
+
navigationStates:
|
|
103
|
+
- installation
|
|
104
|
+
- label: Service and delivery
|
|
105
|
+
navigationStates:
|
|
106
|
+
- service
|
|
107
|
+
- label: Review
|
|
108
|
+
navigationStates:
|
|
109
|
+
- review
|
|
110
|
+
- requested
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`StepHeader` draws them with `phx.frontend.useStepNavigation()`: each step has a `label`, a `status` of `complete`, `active` or `upcoming`, and `goTo(index)` enters it. The hook enforces no order, so the engineer can jump back to any step from the review. A step that holds a conflict carries a mark until it is resolved; which step resolves which invariant is the layout's own map, `STEP_WARNINGS`. `StepForm` renders the active step's sections by switching on `phx.frontend.useActiveNavigationState()`, and the page's **Back** and **Next** buttons call `previous()` and `next()`.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { Box, ButtonBase, Typography } from '@mui/material'
|
|
117
|
+
|
|
118
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
119
|
+
import { ARDENT, useWarnings } from '../_form/index.js'
|
|
120
|
+
import { STEP_WARNINGS } from './step-warnings.js'
|
|
121
|
+
|
|
122
|
+
const CHECK_ICON = 'M5 12.5 10 17.5 19 7'
|
|
123
|
+
|
|
124
|
+
interface StepHeaderProps {
|
|
125
|
+
readonly compact: boolean
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// The spec's `navigationSteps` as a stepper. Every step can be entered, in any order; a step that holds a warning
|
|
129
|
+
// carries a mark until the warning is resolved. On a phone the stepper is one line with a segmented progress bar.
|
|
130
|
+
export const StepHeader = ({ compact }: StepHeaderProps) => {
|
|
131
|
+
const { steps, activeStep, goTo } = phx.frontend.useStepNavigation()
|
|
132
|
+
const warnings = useWarnings()
|
|
133
|
+
|
|
134
|
+
const hasWarning = (states: readonly string[]) => {
|
|
135
|
+
return states.some(state => warnings.some(warning => STEP_WARNINGS[state]?.includes(warning.type)))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (compact) {
|
|
139
|
+
return (
|
|
140
|
+
<Box
|
|
141
|
+
component="nav"
|
|
142
|
+
aria-label="Quote steps"
|
|
143
|
+
sx={{ display: 'grid', gap: 1, paddingInline: 2, paddingBlock: 1.5 }}
|
|
144
|
+
>
|
|
145
|
+
<Box component="div" sx={{ display: 'flex', alignItems: 'baseline', gap: 1 }}>
|
|
146
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 13, color: ARDENT.steel }}>
|
|
147
|
+
Step {(activeStep?.index ?? 0) + 1} of {steps.length}
|
|
148
|
+
</Typography>
|
|
149
|
+
<Typography component="h2" sx={{ fontFamily: ARDENT.font, fontSize: 17, fontWeight: 600, color: ARDENT.ink }}>
|
|
150
|
+
{activeStep?.label}
|
|
151
|
+
</Typography>
|
|
152
|
+
</Box>
|
|
153
|
+
<Box component="ol" sx={{ display: 'flex', gap: 0.5, margin: 0, padding: 0, listStyle: 'none' }}>
|
|
154
|
+
{steps.map(step => (
|
|
155
|
+
<Box component="li" key={step.state} sx={{ flex: 1 }}>
|
|
156
|
+
<ButtonBase
|
|
157
|
+
aria-label={`${step.label}${hasWarning(step.navigationStates) ? ', needs attention' : ''}`}
|
|
158
|
+
aria-current={step.status === 'active' ? 'step' : undefined}
|
|
159
|
+
onClick={() => goTo(step.index)}
|
|
160
|
+
sx={{
|
|
161
|
+
display: 'block',
|
|
162
|
+
inlineSize: '100%',
|
|
163
|
+
blockSize: 6,
|
|
164
|
+
borderRadius: 3,
|
|
165
|
+
// A bigger target than the bar itself, for a finger
|
|
166
|
+
outline: '8px solid transparent',
|
|
167
|
+
bgcolor: hasWarning(step.navigationStates)
|
|
168
|
+
? ARDENT.warning
|
|
169
|
+
: step.status === 'upcoming'
|
|
170
|
+
? ARDENT.line
|
|
171
|
+
: ARDENT.signal,
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
</Box>
|
|
175
|
+
))}
|
|
176
|
+
</Box>
|
|
177
|
+
</Box>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<Box
|
|
183
|
+
component="nav"
|
|
184
|
+
aria-label="Quote steps"
|
|
185
|
+
sx={{ display: 'flex', alignItems: 'center', gap: 1, paddingInline: 4, paddingBlock: 1.5, overflowX: 'auto' }}
|
|
186
|
+
>
|
|
187
|
+
{steps.map((step, index) => {
|
|
188
|
+
const warn = hasWarning(step.navigationStates)
|
|
189
|
+
const isActive = step.status === 'active'
|
|
190
|
+
const isComplete = step.status === 'complete'
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<Box component="div" key={step.state} sx={{ display: 'flex', alignItems: 'center', gap: 1, flex: 'none' }}>
|
|
194
|
+
{index > 0 && (
|
|
195
|
+
<Box
|
|
196
|
+
component="span"
|
|
197
|
+
aria-hidden
|
|
198
|
+
sx={{ inlineSize: 32, blockSize: 2, bgcolor: isComplete || isActive ? ARDENT.signal : ARDENT.line }}
|
|
199
|
+
/>
|
|
200
|
+
)}
|
|
201
|
+
<ButtonBase
|
|
202
|
+
aria-current={isActive ? 'step' : undefined}
|
|
203
|
+
onClick={() => goTo(step.index)}
|
|
204
|
+
sx={{
|
|
205
|
+
display: 'flex',
|
|
206
|
+
alignItems: 'center',
|
|
207
|
+
gap: 1,
|
|
208
|
+
paddingInline: 1,
|
|
209
|
+
paddingBlock: 0.75,
|
|
210
|
+
borderRadius: 2,
|
|
211
|
+
'&:hover': { bgcolor: ARDENT.page },
|
|
212
|
+
'&.Mui-focusVisible': { outline: `2px solid ${ARDENT.navy}` },
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
<Box
|
|
216
|
+
component="span"
|
|
217
|
+
sx={{
|
|
218
|
+
display: 'grid',
|
|
219
|
+
placeItems: 'center',
|
|
220
|
+
inlineSize: 28,
|
|
221
|
+
blockSize: 28,
|
|
222
|
+
borderRadius: '50%',
|
|
223
|
+
fontFamily: ARDENT.font,
|
|
224
|
+
fontSize: 14,
|
|
225
|
+
fontWeight: 600,
|
|
226
|
+
color: isActive || isComplete ? ARDENT.panel : ARDENT.steel,
|
|
227
|
+
bgcolor: isActive ? ARDENT.signal : isComplete ? ARDENT.navy : 'transparent',
|
|
228
|
+
border: isActive || isComplete ? 'none' : `2px solid ${ARDENT.line}`,
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
{isComplete ? (
|
|
232
|
+
<Box component="svg" viewBox="0 0 24 24" aria-hidden sx={{ inlineSize: 16, blockSize: 16 }}>
|
|
233
|
+
<path d={CHECK_ICON} fill="none" stroke="currentColor" strokeWidth={3} strokeLinecap="round" />
|
|
234
|
+
</Box>
|
|
235
|
+
) : (
|
|
236
|
+
index + 1
|
|
237
|
+
)}
|
|
238
|
+
</Box>
|
|
239
|
+
<Typography
|
|
240
|
+
component="span"
|
|
241
|
+
sx={{
|
|
242
|
+
fontFamily: ARDENT.font,
|
|
243
|
+
fontSize: 15,
|
|
244
|
+
fontWeight: isActive ? 600 : 500,
|
|
245
|
+
color: isActive ? ARDENT.ink : ARDENT.steel,
|
|
246
|
+
whiteSpace: 'nowrap',
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
{step.label}
|
|
250
|
+
</Typography>
|
|
251
|
+
{warn && (
|
|
252
|
+
<Box
|
|
253
|
+
component="span"
|
|
254
|
+
role="img"
|
|
255
|
+
aria-label="needs attention"
|
|
256
|
+
sx={{ inlineSize: 8, blockSize: 8, borderRadius: '50%', bgcolor: ARDENT.warning }}
|
|
257
|
+
/>
|
|
258
|
+
)}
|
|
259
|
+
</ButtonBase>
|
|
260
|
+
</Box>
|
|
261
|
+
)
|
|
262
|
+
})}
|
|
263
|
+
</Box>
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Questions that depend on earlier answers
|
|
269
|
+
|
|
270
|
+
A question that only makes sense after an earlier answer is a spec component with a filter. The dew point is asked only with a desiccant dryer, and heat recovery only for a water-cooled machine:
|
|
271
|
+
|
|
272
|
+
```yaml
|
|
273
|
+
components:
|
|
274
|
+
Compressor:
|
|
275
|
+
attributeSelection:
|
|
276
|
+
dewPoint:
|
|
277
|
+
value:
|
|
278
|
+
$bind: dewPoint
|
|
279
|
+
options:
|
|
280
|
+
- label: −40 °C
|
|
281
|
+
description: Outdoor lines, food and pharma
|
|
282
|
+
value: Minus40
|
|
283
|
+
- label: −70 °C
|
|
284
|
+
description: Electronics and laboratories
|
|
285
|
+
value: Minus70
|
|
286
|
+
$filter:
|
|
287
|
+
active:
|
|
288
|
+
$eval: self.dryer === 'Desiccant'
|
|
289
|
+
attributeSwitch:
|
|
290
|
+
heatRecovery:
|
|
291
|
+
value:
|
|
292
|
+
$bind: heatRecovery
|
|
293
|
+
$filter:
|
|
294
|
+
active:
|
|
295
|
+
$eval: self.cooling === 'Water'
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The layout lists every field of a step and lets the spec decide which exist. A control hook such as `phx.frontend.useAttributeSelection()` throws when its component is not active, so `ChoiceField` first asks `phx.frontend.useComponent()` for the component and mounts the cards only when it is there. `SwitchField` does the same for `attributeSwitch`. The value of a hidden question stays in the configuration, so the expressions and the summary read it only where the question applies, such as the dew point of a desiccant dryer.
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
import { Box, ButtonBase, Typography } from '@mui/material'
|
|
302
|
+
import type { KeyboardEvent } from 'react'
|
|
303
|
+
|
|
304
|
+
import { attributeSelectionComponent } from '@salsita-ai/sdk/domain'
|
|
305
|
+
|
|
306
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
307
|
+
import { FieldShell } from './FieldShell.js'
|
|
308
|
+
import { ARDENT } from './tokens.js'
|
|
309
|
+
|
|
310
|
+
// Arrow keys move the selection between the cards, as in a native radio group
|
|
311
|
+
const ARROW_STEPS: Readonly<Record<string, number | undefined>> = {
|
|
312
|
+
ArrowRight: 1,
|
|
313
|
+
ArrowDown: 1,
|
|
314
|
+
ArrowLeft: -1,
|
|
315
|
+
ArrowUp: -1,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
interface ChoiceFieldProps {
|
|
319
|
+
readonly instanceName: string
|
|
320
|
+
readonly label: string
|
|
321
|
+
readonly hint?: string
|
|
322
|
+
readonly warningTypes?: readonly string[]
|
|
323
|
+
readonly readOnly: boolean
|
|
324
|
+
// The narrowest a card gets before the cards wrap onto another row
|
|
325
|
+
readonly minCardWidth?: number
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const ChoiceCards = ({ instanceName, label, hint, warningTypes, readOnly, minCardWidth = 150 }: ChoiceFieldProps) => {
|
|
329
|
+
const resolve = phx.frontend.useResolveLocalizable()
|
|
330
|
+
const compressorId = phx.frontend.useRootEntityId()
|
|
331
|
+
const { value, options } = phx.frontend.useAttributeSelection(compressorId, instanceName)
|
|
332
|
+
// An option a `fixable` or `fatal` invariant rules out is `disabled`, with the reason in its `hint`
|
|
333
|
+
const optionHint = options.find(option => option.disabled && option.hint)?.hint
|
|
334
|
+
|
|
335
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLElement>, index: number) => {
|
|
336
|
+
const step = ARROW_STEPS[event.key]
|
|
337
|
+
|
|
338
|
+
if (step === undefined) {
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
event.preventDefault()
|
|
343
|
+
const next = (index + step + options.length) % options.length
|
|
344
|
+
options[next]?.onSelect()
|
|
345
|
+
event.currentTarget.parentElement?.querySelectorAll<HTMLElement>('[role="radio"]')[next]?.focus()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<FieldShell id={`ardent-${instanceName}`} label={label} hint={optionHint ?? hint} warningTypes={warningTypes}>
|
|
350
|
+
<Box
|
|
351
|
+
component="div"
|
|
352
|
+
role="radiogroup"
|
|
353
|
+
aria-labelledby={`ardent-${instanceName}-label`}
|
|
354
|
+
sx={{ display: 'grid', gridTemplateColumns: `repeat(auto-fit, minmax(${minCardWidth}px, 1fr))`, gap: 1.25 }}
|
|
355
|
+
>
|
|
356
|
+
{options.map((option, index) => {
|
|
357
|
+
const isSelected = option.value === value
|
|
358
|
+
const description = resolve(option.description)
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<ButtonBase
|
|
362
|
+
key={String(option.value)}
|
|
363
|
+
role="radio"
|
|
364
|
+
aria-checked={isSelected}
|
|
365
|
+
aria-disabled={option.disabled}
|
|
366
|
+
tabIndex={isSelected ? 0 : -1}
|
|
367
|
+
disabled={readOnly && !isSelected}
|
|
368
|
+
onClick={readOnly ? undefined : option.onSelect}
|
|
369
|
+
onKeyDown={readOnly ? undefined : event => handleKeyDown(event, index)}
|
|
370
|
+
sx={{
|
|
371
|
+
display: 'grid',
|
|
372
|
+
gridTemplateColumns: 'auto 1fr',
|
|
373
|
+
alignItems: 'start',
|
|
374
|
+
columnGap: 1.25,
|
|
375
|
+
rowGap: 0.25,
|
|
376
|
+
padding: 1.5,
|
|
377
|
+
textAlign: 'start',
|
|
378
|
+
borderRadius: 2,
|
|
379
|
+
border: `1px solid ${isSelected ? ARDENT.signal : ARDENT.line}`,
|
|
380
|
+
boxShadow: isSelected ? `inset 0 0 0 1px ${ARDENT.signal}` : 'none',
|
|
381
|
+
bgcolor: isSelected ? ARDENT.signalTint : ARDENT.panel,
|
|
382
|
+
fontFamily: ARDENT.font,
|
|
383
|
+
opacity: option.disabled ? 0.45 : 1,
|
|
384
|
+
transition: 'border-color 140ms, background-color 140ms',
|
|
385
|
+
'&:hover': { borderColor: isSelected ? ARDENT.signal : ARDENT.steel },
|
|
386
|
+
'&.Mui-focusVisible': { outline: `2px solid ${ARDENT.navy}`, outlineOffset: 2 },
|
|
387
|
+
'&.Mui-disabled': { opacity: 0.45 },
|
|
388
|
+
}}
|
|
389
|
+
>
|
|
390
|
+
{/* The radio mark: a ring, filled with a dot when the card is chosen */}
|
|
391
|
+
<Box
|
|
392
|
+
component="span"
|
|
393
|
+
aria-hidden
|
|
394
|
+
sx={{
|
|
395
|
+
gridRow: 'span 2',
|
|
396
|
+
marginBlockStart: 0.25,
|
|
397
|
+
inlineSize: 18,
|
|
398
|
+
blockSize: 18,
|
|
399
|
+
borderRadius: '50%',
|
|
400
|
+
border: `2px solid ${isSelected ? ARDENT.signal : ARDENT.steel}`,
|
|
401
|
+
boxShadow: isSelected ? `inset 0 0 0 3px ${ARDENT.signalTint}` : 'none',
|
|
402
|
+
bgcolor: isSelected ? ARDENT.signal : 'transparent',
|
|
403
|
+
}}
|
|
404
|
+
/>
|
|
405
|
+
<Typography
|
|
406
|
+
component="span"
|
|
407
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, fontWeight: 600, color: ARDENT.ink }}
|
|
408
|
+
>
|
|
409
|
+
{resolve(option.title)}
|
|
410
|
+
</Typography>
|
|
411
|
+
{description && (
|
|
412
|
+
<Typography
|
|
413
|
+
component="span"
|
|
414
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 13, lineHeight: 1.35, color: ARDENT.steel }}
|
|
415
|
+
>
|
|
416
|
+
{description}
|
|
417
|
+
</Typography>
|
|
418
|
+
)}
|
|
419
|
+
</ButtonBase>
|
|
420
|
+
)
|
|
421
|
+
})}
|
|
422
|
+
</Box>
|
|
423
|
+
</FieldShell>
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// One `attributeSelection` of the spec as a group of cards. The field exists only while the spec's component is
|
|
428
|
+
// active: a `$filter` in the spec decides when a question is asked, and the layout follows it. The control hook throws
|
|
429
|
+
// for an inactive component, so the cards mount only after `useComponent` has found it. An option that a `fixable`
|
|
430
|
+
// invariant rules out is faded with its hint below the cards; it stays clickable, the change is rejected and the
|
|
431
|
+
// built-in snackbar says why.
|
|
432
|
+
export const ChoiceField = (props: ChoiceFieldProps) => {
|
|
433
|
+
const compressorId = phx.frontend.useRootEntityId()
|
|
434
|
+
const component = phx.frontend.useComponent({
|
|
435
|
+
entityId: compressorId,
|
|
436
|
+
componentType: attributeSelectionComponent,
|
|
437
|
+
instanceName: props.instanceName,
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
return component === null ? null : <ChoiceCards {...props} />
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Options that follow the configuration
|
|
445
|
+
|
|
446
|
+
The delivery options name their lead time, and express delivery is offered only for the stock frames up to 110 kW. The options are an expression instead of a list:
|
|
447
|
+
|
|
448
|
+
```yaml
|
|
449
|
+
components:
|
|
450
|
+
Compressor:
|
|
451
|
+
attributeSelection:
|
|
452
|
+
delivery:
|
|
453
|
+
value:
|
|
454
|
+
$bind: delivery
|
|
455
|
+
options:
|
|
456
|
+
$read: $deliveryOptions
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
`$deliveryOptions` in `src/expressions/compressor.ts` returns `{ label, description, value }` entries, "Standard, 6 weeks" and "Express, 3 weeks" for the first configuration. When a larger frame drops the express option while it is chosen, no card is selected and the `expressUnavailable` warning below tells the engineer to choose again.
|
|
460
|
+
|
|
461
|
+
## Validation from invariants
|
|
462
|
+
|
|
463
|
+
The rules between choices are invariants of the spec. Every one is a `warning`: a change that breaks it is kept, so the engineer sees the conflict and fixes it where it is shown, and finalizing waits until no warning is left. A `fixable` or `fatal` invariant would reject the change instead, as the [Kestrel Kettle](?path=/docs/layouts-gallery-kestrel-kettle--docs#a-rule-between-two-choices) shows. Two rules name numbers in their message, so they are declared with `types` and build the message in code.
|
|
464
|
+
|
|
465
|
+
```yaml
|
|
466
|
+
invariants:
|
|
467
|
+
Compressor:
|
|
468
|
+
flowRange:
|
|
469
|
+
types:
|
|
470
|
+
- flowAboveRange
|
|
471
|
+
desiccantNeedsFiltration:
|
|
472
|
+
type: desiccantNeedsFiltration
|
|
473
|
+
severity: warning
|
|
474
|
+
message: A desiccant dryer needs high-efficiency or oil-vapour filtration upstream, or oil aerosol spoils the desiccant.
|
|
475
|
+
receiverSize:
|
|
476
|
+
types:
|
|
477
|
+
- receiverTooSmall
|
|
478
|
+
expressUnavailable:
|
|
479
|
+
type: expressUnavailable
|
|
480
|
+
severity: warning
|
|
481
|
+
message: Express delivery covers stock frames up to 110 kW. Choose standard delivery for this frame.
|
|
482
|
+
companyMissing:
|
|
483
|
+
type: companyMissing
|
|
484
|
+
severity: warning
|
|
485
|
+
message: Enter the company the quote is for.
|
|
486
|
+
emailInvalid:
|
|
487
|
+
type: emailInvalid
|
|
488
|
+
severity: warning
|
|
489
|
+
message: Enter a valid email address for the quote.
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
494
|
+
|
|
495
|
+
const PRESSURE_LABEL = { Bar7: '7.5 bar', Bar10: '10 bar', Bar13: '13 bar' }
|
|
496
|
+
const RECEIVER_LITRES = { None: 0, Litres500: 500, Litres1000: 1000, Litres2000: 2000 }
|
|
497
|
+
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/
|
|
498
|
+
|
|
499
|
+
// Each rule is a `warning` in the spec: a change that breaks it is kept, so the customer can fix the conflict where it
|
|
500
|
+
// is shown, and finalizing waits until no warning is left
|
|
501
|
+
export default phx.domain.implementCustomInvariants({
|
|
502
|
+
Compressor: {
|
|
503
|
+
// The largest frame delivers less air at a higher pressure, so the limit depends on an earlier choice
|
|
504
|
+
flowRange: s => {
|
|
505
|
+
if (s.flow <= s.$maxFlow) {
|
|
506
|
+
return []
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return [
|
|
510
|
+
{
|
|
511
|
+
type: 'flowAboveRange',
|
|
512
|
+
severity: 'warning',
|
|
513
|
+
message: `At ${PRESSURE_LABEL[s.pressure]} the largest frame delivers ${s.$maxFlow.toFixed(1)} m³/min. Lower the flow or the pressure.`,
|
|
514
|
+
},
|
|
515
|
+
]
|
|
516
|
+
},
|
|
517
|
+
desiccantNeedsFiltration: s => {
|
|
518
|
+
return s.dryer !== 'Desiccant' || s.filtration !== 'General'
|
|
519
|
+
},
|
|
520
|
+
// The message names the size the flow needs, so it is built here rather than in the spec
|
|
521
|
+
receiverSize: s => {
|
|
522
|
+
if (RECEIVER_LITRES[s.receiver] >= s.$minReceiverLitres) {
|
|
523
|
+
return []
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return [
|
|
527
|
+
{
|
|
528
|
+
type: 'receiverTooSmall',
|
|
529
|
+
severity: 'warning',
|
|
530
|
+
message: `A fixed-speed compressor at ${s.flow.toFixed(1)} m³/min needs a receiver of at least ${s.$minReceiverLitres.toLocaleString('en')} L.`,
|
|
531
|
+
},
|
|
532
|
+
]
|
|
533
|
+
},
|
|
534
|
+
expressUnavailable: s => {
|
|
535
|
+
return s.delivery !== 'Express' || s.$frameKw <= 110
|
|
536
|
+
},
|
|
537
|
+
companyMissing: s => {
|
|
538
|
+
return s.company.trim().length > 0
|
|
539
|
+
},
|
|
540
|
+
emailInvalid: s => {
|
|
541
|
+
return EMAIL.test(s.email.trim())
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
})
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
The flow's limit depends on the pressure: at 13 bar the largest frame delivers less air. The `flow` dimension keeps a fixed range of 2 to 40 m³/min and the `flowAboveRange` warning carries the limit. A `max` read from an expression would not do: a pressure change that leaves the flow above a dimension's `max` is rejected with a built-in message, so the engineer could not choose 13 bar at all.
|
|
548
|
+
|
|
549
|
+
`useWarnings` resolves the warnings of the committed configuration. It reads them from `phx.frontend.useFinalizeViolations()`, which lists the violations that block finalizing; `phx.frontend.useStateValidity()` holds the same list, but on `phx.frontend` it is typed as the value it returns rather than as a hook, so a call does not type-check.
|
|
550
|
+
|
|
551
|
+
```ts
|
|
552
|
+
import { useMemo } from 'react'
|
|
553
|
+
|
|
554
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
555
|
+
|
|
556
|
+
export interface Warning {
|
|
557
|
+
readonly type: string
|
|
558
|
+
readonly text: string
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// The warnings of the committed configuration with their text resolved. A `warning` invariant keeps the change, so its
|
|
562
|
+
// violation stays in the committed state until the customer resolves it, and finalizing waits for that.
|
|
563
|
+
// `useFinalizeViolations` lists them; `useStateValidity` holds the same list, but on `phx.frontend` it is typed as the
|
|
564
|
+
// validity value instead of a hook, so calling it does not type-check.
|
|
565
|
+
export const useWarnings = (): readonly Warning[] => {
|
|
566
|
+
const { violations } = phx.frontend.useFinalizeViolations()
|
|
567
|
+
const resolve = phx.frontend.useResolveLocalizable()
|
|
568
|
+
|
|
569
|
+
return useMemo(() => {
|
|
570
|
+
return (violations ?? []).map(({ violation }) => ({ type: violation.type, text: resolve(violation.message) ?? '' }))
|
|
571
|
+
}, [violations, resolve])
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
`FieldShell` shows a field's warning below its input in place of the help text. A field names the types it can resolve, so the receiver field shows the receiver rule and the filter package the desiccant rule. The contact fields on the review step show theirs once the engineer has left the field, or after a request was attempted. The review step also lists every conflict left, each with a button to the step that resolves it.
|
|
576
|
+
|
|
577
|
+
## A live price breakdown
|
|
578
|
+
|
|
579
|
+
Every number of the breakdown is an expression. Declaring them in `expressions:` types them for the layout: `phx.frontend.useRootEntityState()` returns the root entity with its expressions, so `$quoteLines` is an array of `{ key, label, detail, amount }`. An expression that is only inferred from where the spec reads it gets the type of that place, which can be wider than the layout wants.
|
|
580
|
+
|
|
581
|
+
```yaml
|
|
582
|
+
expressions:
|
|
583
|
+
Compressor:
|
|
584
|
+
$leadTimeWeeks:
|
|
585
|
+
type: number
|
|
586
|
+
$quoteLines:
|
|
587
|
+
type: array
|
|
588
|
+
items:
|
|
589
|
+
type: object
|
|
590
|
+
properties:
|
|
591
|
+
key:
|
|
592
|
+
type: string
|
|
593
|
+
label:
|
|
594
|
+
type: string
|
|
595
|
+
detail:
|
|
596
|
+
type: string
|
|
597
|
+
amount:
|
|
598
|
+
type: number
|
|
599
|
+
required:
|
|
600
|
+
- key
|
|
601
|
+
- label
|
|
602
|
+
- detail
|
|
603
|
+
- amount
|
|
604
|
+
additionalProperties: false
|
|
605
|
+
$subtotal:
|
|
606
|
+
type: number
|
|
607
|
+
$leadTimeSurcharge:
|
|
608
|
+
type: number
|
|
609
|
+
$total:
|
|
610
|
+
type: number
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
`src/expressions/compressor.ts` picks the smallest frame that delivers the flow at the pressure and prices it, then adds a line for each option that costs extra: the variable speed drive, the dryer, the filters, water cooling and heat recovery, the receiver, the canopy, the 60 Hz build, the service plan and commissioning. Express delivery adds 8 % of the subtotal. The `price` component reads `$total`, so `phx.frontend.useRootEntityPrice()` formats the total in the component's currency:
|
|
614
|
+
|
|
615
|
+
```yaml
|
|
616
|
+
components:
|
|
617
|
+
Compressor:
|
|
618
|
+
price:
|
|
619
|
+
main:
|
|
620
|
+
price:
|
|
621
|
+
$read: $total
|
|
622
|
+
currency: EUR
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
```tsx
|
|
626
|
+
import { Box, Typography } from '@mui/material'
|
|
627
|
+
|
|
628
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
629
|
+
import { ARDENT, OVERLINE_SX } from '../_form/index.js'
|
|
630
|
+
|
|
631
|
+
const formatEuro = (amount: number) => {
|
|
632
|
+
return amount.toLocaleString('en-IE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 })
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const ROW_SX = {
|
|
636
|
+
display: 'grid',
|
|
637
|
+
gridTemplateColumns: '1fr auto',
|
|
638
|
+
columnGap: 2,
|
|
639
|
+
alignItems: 'baseline',
|
|
640
|
+
paddingBlock: 1,
|
|
641
|
+
} as const
|
|
642
|
+
|
|
643
|
+
// The quote's lines, subtotal, lead-time surcharge and total. Every number comes from an expression of the spec
|
|
644
|
+
// (`$quoteLines`, `$subtotal`, `$leadTimeSurcharge`, `$leadTimeWeeks`), read from the root entity, so the breakdown
|
|
645
|
+
// changes with each choice, and the total is the `price` component's value.
|
|
646
|
+
export const PriceBreakdown = () => {
|
|
647
|
+
const compressor = phx.frontend.useRootEntityState()
|
|
648
|
+
const total = phx.frontend.useRootEntityPrice()
|
|
649
|
+
|
|
650
|
+
return (
|
|
651
|
+
<Box component="section" aria-label="Price breakdown" sx={{ display: 'grid', fontFamily: ARDENT.font }}>
|
|
652
|
+
<Typography component="h2" sx={{ ...OVERLINE_SX, paddingBlockEnd: 1 }}>
|
|
653
|
+
Price breakdown
|
|
654
|
+
</Typography>
|
|
655
|
+
|
|
656
|
+
<Box component="ul" sx={{ margin: 0, padding: 0, listStyle: 'none' }}>
|
|
657
|
+
{compressor.$quoteLines.map(line => (
|
|
658
|
+
<Box component="li" key={line.key} sx={{ ...ROW_SX, borderBlockEnd: `1px solid ${ARDENT.line}` }}>
|
|
659
|
+
<Box component="div" sx={{ display: 'grid', minInlineSize: 0 }}>
|
|
660
|
+
<Typography
|
|
661
|
+
component="span"
|
|
662
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, fontWeight: 500, color: ARDENT.ink }}
|
|
663
|
+
>
|
|
664
|
+
{line.label}
|
|
665
|
+
</Typography>
|
|
666
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 13, color: ARDENT.steel }}>
|
|
667
|
+
{line.detail}
|
|
668
|
+
</Typography>
|
|
669
|
+
</Box>
|
|
670
|
+
<Typography
|
|
671
|
+
component="span"
|
|
672
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.ink, fontVariantNumeric: 'tabular-nums' }}
|
|
673
|
+
>
|
|
674
|
+
{formatEuro(line.amount)}
|
|
675
|
+
</Typography>
|
|
676
|
+
</Box>
|
|
677
|
+
))}
|
|
678
|
+
</Box>
|
|
679
|
+
|
|
680
|
+
<Box component="div" sx={{ ...ROW_SX, paddingBlockStart: 1.5 }}>
|
|
681
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.steel }}>
|
|
682
|
+
Subtotal
|
|
683
|
+
</Typography>
|
|
684
|
+
<Typography
|
|
685
|
+
component="span"
|
|
686
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.ink, fontVariantNumeric: 'tabular-nums' }}
|
|
687
|
+
>
|
|
688
|
+
{formatEuro(compressor.$subtotal)}
|
|
689
|
+
</Typography>
|
|
690
|
+
</Box>
|
|
691
|
+
{compressor.$leadTimeSurcharge > 0 && (
|
|
692
|
+
<Box component="div" sx={ROW_SX}>
|
|
693
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.steel }}>
|
|
694
|
+
Express delivery surcharge
|
|
695
|
+
</Typography>
|
|
696
|
+
<Typography
|
|
697
|
+
component="span"
|
|
698
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.ink, fontVariantNumeric: 'tabular-nums' }}
|
|
699
|
+
>
|
|
700
|
+
{formatEuro(compressor.$leadTimeSurcharge)}
|
|
701
|
+
</Typography>
|
|
702
|
+
</Box>
|
|
703
|
+
)}
|
|
704
|
+
|
|
705
|
+
<Box
|
|
706
|
+
component="div"
|
|
707
|
+
sx={{ ...ROW_SX, marginBlockStart: 1, paddingBlockStart: 1.5, borderBlockStart: `2px solid ${ARDENT.ink}` }}
|
|
708
|
+
>
|
|
709
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 17, fontWeight: 600, color: ARDENT.ink }}>
|
|
710
|
+
Total, ex works
|
|
711
|
+
</Typography>
|
|
712
|
+
<Typography
|
|
713
|
+
component="span"
|
|
714
|
+
sx={{
|
|
715
|
+
fontFamily: ARDENT.font,
|
|
716
|
+
fontSize: 26,
|
|
717
|
+
fontWeight: 700,
|
|
718
|
+
color: ARDENT.ink,
|
|
719
|
+
fontVariantNumeric: 'tabular-nums',
|
|
720
|
+
}}
|
|
721
|
+
>
|
|
722
|
+
{total}
|
|
723
|
+
</Typography>
|
|
724
|
+
</Box>
|
|
725
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 14, color: ARDENT.steel, textAlign: 'end' }}>
|
|
726
|
+
Lead time {compressor.$leadTimeWeeks} weeks · prices excl. VAT
|
|
727
|
+
</Typography>
|
|
728
|
+
</Box>
|
|
729
|
+
)
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
On a desktop the breakdown is a column beside the form. On a phone the total sits in the bottom bar and opens the breakdown as a sheet.
|
|
734
|
+
|
|
735
|
+
## Request a quote
|
|
736
|
+
|
|
737
|
+
The spec declares one finalize action. Its handler is project code registered with `phx.frontend.createPhoenixHandler`; in a deployed project it sends the configuration to the project's finalize webhook (see [Finalization](?path=/docs/phoenix-finalization--docs) and [Webhooks](?path=/docs/phoenix-webhooks--docs)), and a CPQ system behind that webhook prices and answers. Storybook has no server, so this handler waits and answers with a fixed engineer and a reference made from the configuration's id.
|
|
738
|
+
|
|
739
|
+
```yaml
|
|
740
|
+
components:
|
|
741
|
+
Compressor:
|
|
742
|
+
finalizeAction:
|
|
743
|
+
quote:
|
|
744
|
+
variant: primary
|
|
745
|
+
label: Request quote
|
|
746
|
+
title: Send the configuration to an application engineer for a firm quote
|
|
747
|
+
icon: PaperPlaneTilt
|
|
748
|
+
handler: requestQuote
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
```ts
|
|
752
|
+
import { useCallback } from 'react'
|
|
753
|
+
|
|
754
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
755
|
+
import { setQuoteReceipt } from '../_shared/quote-receipt.js'
|
|
756
|
+
|
|
757
|
+
const VALID_DAYS = 30
|
|
758
|
+
|
|
759
|
+
// Stands in for Ardent's quote service. A deployed project sends the configuration to its finalize webhook here and
|
|
760
|
+
// waits for the answer; this one answers after a moment with a fixed engineer and a reference made from the
|
|
761
|
+
// configuration's id, then enters the `requested` navigation state.
|
|
762
|
+
export default phx.frontend.createPhoenixHandler('requestQuote', () => {
|
|
763
|
+
const navigate = phx.frontend.useNavigate()
|
|
764
|
+
const configurationId = phx.frontend.useConfigurationId()
|
|
765
|
+
const total = phx.frontend.useRootEntityPrice()
|
|
766
|
+
|
|
767
|
+
return {
|
|
768
|
+
handler: useCallback(async () => {
|
|
769
|
+
await new Promise(resolve => setTimeout(resolve, 1600))
|
|
770
|
+
|
|
771
|
+
const validUntil = new Date(Date.now() + VALID_DAYS * 24 * 60 * 60 * 1000)
|
|
772
|
+
|
|
773
|
+
setQuoteReceipt({
|
|
774
|
+
reference: `AQ-${String(configurationId)
|
|
775
|
+
.replace(/[^a-z0-9]/gi, '')
|
|
776
|
+
.slice(-6)
|
|
777
|
+
.toUpperCase()}`,
|
|
778
|
+
engineer: 'Marta Kovács, application engineer',
|
|
779
|
+
validUntil: validUntil.toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }),
|
|
780
|
+
total: total ?? '',
|
|
781
|
+
})
|
|
782
|
+
navigate('requested')
|
|
783
|
+
}, [configurationId, navigate, total]),
|
|
784
|
+
}
|
|
785
|
+
})
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
A finalize action's `run()` resolves without a value, so the answer has no way back to the layout through the action. The handler keeps it in a small store of the project's own, which the review step reads:
|
|
789
|
+
|
|
790
|
+
```ts
|
|
791
|
+
import { useSyncExternalStore } from 'react'
|
|
792
|
+
|
|
793
|
+
/** What the quote service answers when a quote is requested: the reference the engineer will quote against */
|
|
794
|
+
export interface QuoteReceipt {
|
|
795
|
+
readonly reference: string
|
|
796
|
+
readonly engineer: string
|
|
797
|
+
readonly validUntil: string
|
|
798
|
+
readonly total: string
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
let receipt: QuoteReceipt | null = null
|
|
802
|
+
const listeners = new Set<() => void>()
|
|
803
|
+
|
|
804
|
+
const subscribe = (listener: () => void) => {
|
|
805
|
+
listeners.add(listener)
|
|
806
|
+
|
|
807
|
+
return () => {
|
|
808
|
+
listeners.delete(listener)
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// A finalize action's `run()` resolves without a value, so the `requestQuote` handler keeps the service's answer here
|
|
813
|
+
// for the layout to show. The project owns this store; the configurator does not persist it.
|
|
814
|
+
export const setQuoteReceipt = (next: QuoteReceipt | null) => {
|
|
815
|
+
receipt = next
|
|
816
|
+
listeners.forEach(listener => listener())
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export const useQuoteReceipt = () => {
|
|
820
|
+
return useSyncExternalStore(subscribe, () => receipt)
|
|
821
|
+
}
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
`QuoteAction` runs the action from `phx.frontend.useFinalizeActions().primary`. While a warning holds, the action is `disabled` and `hints` lists the warnings; the button stays clickable and, instead of running, reveals the warnings of the contact fields the engineer has not visited yet. During the request the button shows a spinner and "Sending to an engineer…"; afterwards the review step shows the reference, the engineer, the validity and the quoted total, and **Back to summary** returns to the `review` state. A failed request would show the handler's error below the button. A viewer of a shared configuration sees no button.
|
|
825
|
+
|
|
826
|
+
```tsx
|
|
827
|
+
import { Alert, Box, Button, CircularProgress, Typography } from '@mui/material'
|
|
828
|
+
|
|
829
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
830
|
+
import { ARDENT } from '../_form/index.js'
|
|
831
|
+
|
|
832
|
+
interface QuoteActionProps {
|
|
833
|
+
// Called instead of running the action while a warning blocks it, so the layout can show every warning
|
|
834
|
+
readonly onBlocked: () => void
|
|
835
|
+
// Leaves out the list of what blocks the request, for the phone's bottom bar
|
|
836
|
+
readonly compact: boolean
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// The spec's `quote` finalize action. While any warning holds, `useFinalizeActions` reports the action `disabled` with
|
|
840
|
+
// the warnings as `hints`: the button stays clickable and reveals them instead of running. A viewer sees no button.
|
|
841
|
+
export const QuoteAction = ({ onBlocked, compact }: QuoteActionProps) => {
|
|
842
|
+
const { primary, hints } = phx.frontend.useFinalizeActions()
|
|
843
|
+
const { accessInfo } = phx.frontend.useAccessService()
|
|
844
|
+
|
|
845
|
+
if (primary === null || accessInfo.viewMode) {
|
|
846
|
+
return null
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return (
|
|
850
|
+
<Box component="div" sx={{ display: 'grid', gap: 1.25 }}>
|
|
851
|
+
<Button
|
|
852
|
+
variant="contained"
|
|
853
|
+
disableElevation
|
|
854
|
+
disabled={primary.isPending}
|
|
855
|
+
aria-disabled={primary.disabled}
|
|
856
|
+
title={primary.title}
|
|
857
|
+
onClick={() => (primary.disabled ? onBlocked() : void primary.run())}
|
|
858
|
+
startIcon={primary.isPending ? <CircularProgress size={18} color="inherit" /> : undefined}
|
|
859
|
+
sx={{
|
|
860
|
+
minBlockSize: 52,
|
|
861
|
+
borderRadius: 2,
|
|
862
|
+
bgcolor: primary.disabled ? ARDENT.steel : ARDENT.signal,
|
|
863
|
+
color: ARDENT.panel,
|
|
864
|
+
fontFamily: ARDENT.font,
|
|
865
|
+
fontSize: 17,
|
|
866
|
+
fontWeight: 600,
|
|
867
|
+
textTransform: 'none',
|
|
868
|
+
'&:hover': { bgcolor: primary.disabled ? ARDENT.steel : ARDENT.signalDark },
|
|
869
|
+
'&.Mui-disabled': { bgcolor: ARDENT.signal, color: ARDENT.panel, opacity: 0.7 },
|
|
870
|
+
}}
|
|
871
|
+
>
|
|
872
|
+
{primary.isPending ? 'Sending to an engineer…' : primary.label}
|
|
873
|
+
</Button>
|
|
874
|
+
{!compact && primary.disabled && hints && (
|
|
875
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 14, lineHeight: 1.4, color: ARDENT.warning }}>
|
|
876
|
+
Resolve before requesting: {hints}
|
|
877
|
+
</Typography>
|
|
878
|
+
)}
|
|
879
|
+
{primary.error && (
|
|
880
|
+
<Alert severity="error">The quote could not be sent: {primary.error.message} Try again in a moment.</Alert>
|
|
881
|
+
)}
|
|
882
|
+
</Box>
|
|
883
|
+
)
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
## On a phone
|
|
888
|
+
|
|
889
|
+
The same layout in a frame 390 pixels wide. The stepper is one line with a segmented bar, the form scrolls, and the bottom bar holds the total, the lead time and the way on: **Next** on every step, **Request quote** on the review. Tapping the total opens the breakdown above the bar. Opened on its own from the sidebar, the **Phone** story sets Storybook's viewport to 390 × 844.
|
|
890
|
+
|
|
891
|
+
## Test cases
|
|
892
|
+
|
|
893
|
+
- The page opens on **Duty** with the RSV-75 frame selected for 9 m³/min at 10 bar. The breakdown lists the compressor, the refrigerant dryer, the 500 L receiver, the Essential plan and commissioning, a total of **€40,975.00** and a lead time of 6 weeks.
|
|
894
|
+
- Typing 30 into the flow selects the RSV-250 frame and the total rises to €111,640.00; the stepper marks **Installation**, where a fixed-speed machine at this flow needs a 1,200 L receiver.
|
|
895
|
+
- Choosing **13 bar** with a flow of 30 shows "At 13 bar the largest frame delivers 29.5 m³/min. Lower the flow or the pressure." under the flow and marks **Duty**; choosing 10 bar again removes it.
|
|
896
|
+
- **Variable speed** adds the "Variable speed drive" line and clears the receiver mark.
|
|
897
|
+
- On **Air treatment**, **Desiccant** makes **Pressure dew point** appear and shows the filtration warning under **Filter package**; **High efficiency** clears it; **Refrigerant** removes the dew point question again.
|
|
898
|
+
- On **Installation**, **Water-cooled** makes **Heat recovery** appear; turning it on adds the "Heat recovery" line.
|
|
899
|
+
- On **Service and delivery** with the RSV-250 frame, **Delivery** offers only standard delivery. With a flow of 12 it also offers express; choosing it adds the "Express delivery surcharge" and shortens the lead time.
|
|
900
|
+
- Raising the flow to 25 while express is chosen shows "Express delivery covers stock frames up to 110 kW. …" under **Delivery**.
|
|
901
|
+
- The **Review** step lists the conflict with **Go to Service and delivery**. **Request quote** reveals "Enter the company the quote is for." and "Enter a valid email address for the quote." under the empty contact fields and does not send.
|
|
902
|
+
- **Go to Service and delivery** and **Standard** delivery clear the conflict: the mark leaves the stepper and the list leaves the review.
|
|
903
|
+
- An email without a domain shows the email warning after leaving the field; a valid company and email clear both warnings and the text under **Request quote**.
|
|
904
|
+
- **Request quote** shows a spinner and "Sending to an engineer…" for about two seconds, then "Quote requested" with a reference, the engineer, the date the price is valid until and the quoted total. **Back to summary** returns to the contact form with its values.
|
|
905
|
+
- **Undo** reverts the last change and **Redo** repeats it.
|
|
906
|
+
- In the **Phone** story, **Next** walks through the steps, the total opens and closes the breakdown, **Request quote** on the review reveals the contact warnings, and a filled form shows "Quote requested".
|
|
907
|
+
- The browser console shows no errors, the page has no `canvas` element, and no model or texture is requested.
|
|
908
|
+
|
|
909
|
+
## What this layout leaves out
|
|
910
|
+
|
|
911
|
+
- **Read mode.** The fields are inert and **Request quote** is hidden while `phx.frontend.useAccessService().accessInfo.viewMode` is set, but the example ships no read template; [Custom Layout → Serve read mode](?path=/docs/layouts-custom-layout--docs#serve-read-mode) shows one.
|
|
912
|
+
- **A quote that outlives the page.** The answer lives in the project's store for as long as the page; a configuration reopened in the `requested` state shows "Quote requested" without the details. A deployed project keeps the answer where its webhook stores it.
|
|
913
|
+
- **The built-in arrangement.** Header, sidebar, footer, tabs and the preview start screen are not mounted; [Layout Slots](?path=/docs/phoenix-layout-slots--docs) lists every slot a project layout can mount.
|
|
914
|
+
|
|
915
|
+
## Related pages
|
|
916
|
+
|
|
917
|
+
- [Fenwick Window](?path=/docs/layouts-gallery-fenwick-window--docs) draws the configuration as a shop drawing, and [Sunfold Solar](?path=/docs/layouts-gallery-sunfold-solar--docs) shows it as a dashboard; neither mounts a canvas either.
|
|
918
|
+
- [Custom Layout](?path=/docs/layouts-custom-layout--docs) explains a project layout step by step.
|
|
919
|
+
- [Configurator Interface → Hooks for a Project Layout](?path=/docs/phoenix-configurator-interface--docs#hooks-for-a-project-layout) lists the hooks used here.
|
|
920
|
+
- [Domain Model → Invariants](?path=/docs/phoenix-domain-model--docs#invariants) and [Domain Model → Expressions](?path=/docs/phoenix-domain-model--docs#expressions) describe the rules and the computed values.
|
|
921
|
+
- [Finalization](?path=/docs/phoenix-finalization--docs) describes the server side of a finalize action.
|
|
922
|
+
|
|
923
|
+
## Complete Implementation
|
|
924
|
+
|
|
925
|
+
{/* AUTO-GENERATED SECTION: This section is automatically updated by 'phoenix story build' */}
|
|
926
|
+
|
|
927
|
+
<details>
|
|
928
|
+
<summary>Click to view the complete implementation files</summary>
|
|
929
|
+
|
|
930
|
+
### ./spec/domain.yaml
|
|
931
|
+
|
|
932
|
+
```yaml
|
|
933
|
+
model:
|
|
934
|
+
latest:
|
|
935
|
+
entityTypes:
|
|
936
|
+
- Compressor
|
|
937
|
+
aliases:
|
|
938
|
+
RootEntity:
|
|
939
|
+
- Compressor
|
|
940
|
+
commonTypes:
|
|
941
|
+
Pressure:
|
|
942
|
+
enum:
|
|
943
|
+
- Bar7
|
|
944
|
+
- Bar10
|
|
945
|
+
- Bar13
|
|
946
|
+
DutyCycle:
|
|
947
|
+
enum:
|
|
948
|
+
- Intermittent
|
|
949
|
+
- Continuous
|
|
950
|
+
Drive:
|
|
951
|
+
enum:
|
|
952
|
+
- FixedSpeed
|
|
953
|
+
- VariableSpeed
|
|
954
|
+
Dryer:
|
|
955
|
+
enum:
|
|
956
|
+
- None
|
|
957
|
+
- Refrigerant
|
|
958
|
+
- Desiccant
|
|
959
|
+
DewPoint:
|
|
960
|
+
enum:
|
|
961
|
+
- Minus40
|
|
962
|
+
- Minus70
|
|
963
|
+
Filtration:
|
|
964
|
+
enum:
|
|
965
|
+
- General
|
|
966
|
+
- HighEfficiency
|
|
967
|
+
- OilVapour
|
|
968
|
+
Cooling:
|
|
969
|
+
enum:
|
|
970
|
+
- Air
|
|
971
|
+
- Water
|
|
972
|
+
Receiver:
|
|
973
|
+
enum:
|
|
974
|
+
- None
|
|
975
|
+
- Litres500
|
|
976
|
+
- Litres1000
|
|
977
|
+
- Litres2000
|
|
978
|
+
Enclosure:
|
|
979
|
+
enum:
|
|
980
|
+
- Standard
|
|
981
|
+
- LowNoise
|
|
982
|
+
Supply:
|
|
983
|
+
enum:
|
|
984
|
+
- V400Hz50
|
|
985
|
+
- V480Hz60
|
|
986
|
+
ServicePlan:
|
|
987
|
+
enum:
|
|
988
|
+
- None
|
|
989
|
+
- Essential
|
|
990
|
+
- TotalCare
|
|
991
|
+
Delivery:
|
|
992
|
+
enum:
|
|
993
|
+
- Standard
|
|
994
|
+
- Express
|
|
995
|
+
shapes:
|
|
996
|
+
Compressor:
|
|
997
|
+
flow:
|
|
998
|
+
type: number
|
|
999
|
+
pressure:
|
|
1000
|
+
$ref: '#/model/latest/commonTypes/Pressure'
|
|
1001
|
+
dutyCycle:
|
|
1002
|
+
$ref: '#/model/latest/commonTypes/DutyCycle'
|
|
1003
|
+
drive:
|
|
1004
|
+
$ref: '#/model/latest/commonTypes/Drive'
|
|
1005
|
+
dryer:
|
|
1006
|
+
$ref: '#/model/latest/commonTypes/Dryer'
|
|
1007
|
+
dewPoint:
|
|
1008
|
+
$ref: '#/model/latest/commonTypes/DewPoint'
|
|
1009
|
+
filtration:
|
|
1010
|
+
$ref: '#/model/latest/commonTypes/Filtration'
|
|
1011
|
+
cooling:
|
|
1012
|
+
$ref: '#/model/latest/commonTypes/Cooling'
|
|
1013
|
+
heatRecovery:
|
|
1014
|
+
type: boolean
|
|
1015
|
+
receiver:
|
|
1016
|
+
$ref: '#/model/latest/commonTypes/Receiver'
|
|
1017
|
+
enclosure:
|
|
1018
|
+
$ref: '#/model/latest/commonTypes/Enclosure'
|
|
1019
|
+
supply:
|
|
1020
|
+
$ref: '#/model/latest/commonTypes/Supply'
|
|
1021
|
+
servicePlan:
|
|
1022
|
+
$ref: '#/model/latest/commonTypes/ServicePlan'
|
|
1023
|
+
commissioning:
|
|
1024
|
+
type: boolean
|
|
1025
|
+
delivery:
|
|
1026
|
+
$ref: '#/model/latest/commonTypes/Delivery'
|
|
1027
|
+
company:
|
|
1028
|
+
type: string
|
|
1029
|
+
email:
|
|
1030
|
+
type: string
|
|
1031
|
+
projectReference:
|
|
1032
|
+
type: string
|
|
1033
|
+
|
|
1034
|
+
presets:
|
|
1035
|
+
- rootEntityType: Compressor
|
|
1036
|
+
initialDomainEntities:
|
|
1037
|
+
- id: Compressor-1
|
|
1038
|
+
type: Compressor
|
|
1039
|
+
flow: 9
|
|
1040
|
+
pressure: Bar10
|
|
1041
|
+
dutyCycle: Intermittent
|
|
1042
|
+
drive: FixedSpeed
|
|
1043
|
+
dryer: Refrigerant
|
|
1044
|
+
dewPoint: Minus40
|
|
1045
|
+
filtration: General
|
|
1046
|
+
cooling: Air
|
|
1047
|
+
heatRecovery: false
|
|
1048
|
+
receiver: Litres500
|
|
1049
|
+
enclosure: Standard
|
|
1050
|
+
supply: V400Hz50
|
|
1051
|
+
servicePlan: Essential
|
|
1052
|
+
commissioning: true
|
|
1053
|
+
delivery: Standard
|
|
1054
|
+
company: ''
|
|
1055
|
+
email: ''
|
|
1056
|
+
projectReference: ''
|
|
1057
|
+
|
|
1058
|
+
navigationStates:
|
|
1059
|
+
- duty
|
|
1060
|
+
- treatment
|
|
1061
|
+
- installation
|
|
1062
|
+
- service
|
|
1063
|
+
- review
|
|
1064
|
+
- requested
|
|
1065
|
+
|
|
1066
|
+
# The quote's price, lead time and summary: declared here, computed in src/expressions/compressor.ts
|
|
1067
|
+
expressions:
|
|
1068
|
+
Compressor:
|
|
1069
|
+
$maxFlow:
|
|
1070
|
+
type: number
|
|
1071
|
+
$frameName:
|
|
1072
|
+
type: string
|
|
1073
|
+
$frameKw:
|
|
1074
|
+
type: number
|
|
1075
|
+
$minReceiverLitres:
|
|
1076
|
+
type: number
|
|
1077
|
+
$leadTimeWeeks:
|
|
1078
|
+
type: number
|
|
1079
|
+
$quoteLines:
|
|
1080
|
+
type: array
|
|
1081
|
+
items:
|
|
1082
|
+
type: object
|
|
1083
|
+
properties:
|
|
1084
|
+
key:
|
|
1085
|
+
type: string
|
|
1086
|
+
label:
|
|
1087
|
+
type: string
|
|
1088
|
+
detail:
|
|
1089
|
+
type: string
|
|
1090
|
+
amount:
|
|
1091
|
+
type: number
|
|
1092
|
+
required:
|
|
1093
|
+
- key
|
|
1094
|
+
- label
|
|
1095
|
+
- detail
|
|
1096
|
+
- amount
|
|
1097
|
+
additionalProperties: false
|
|
1098
|
+
$subtotal:
|
|
1099
|
+
type: number
|
|
1100
|
+
$leadTimeSurcharge:
|
|
1101
|
+
type: number
|
|
1102
|
+
$total:
|
|
1103
|
+
type: number
|
|
1104
|
+
$specSheet:
|
|
1105
|
+
type: array
|
|
1106
|
+
items:
|
|
1107
|
+
type: object
|
|
1108
|
+
properties:
|
|
1109
|
+
label:
|
|
1110
|
+
type: string
|
|
1111
|
+
value:
|
|
1112
|
+
type: string
|
|
1113
|
+
required:
|
|
1114
|
+
- label
|
|
1115
|
+
- value
|
|
1116
|
+
additionalProperties: false
|
|
1117
|
+
|
|
1118
|
+
# Every rule is a warning: the change is kept, the layout shows the message at the field, and finalizing waits
|
|
1119
|
+
# until it is resolved
|
|
1120
|
+
invariants:
|
|
1121
|
+
Compressor:
|
|
1122
|
+
flowRange:
|
|
1123
|
+
types:
|
|
1124
|
+
- flowAboveRange
|
|
1125
|
+
desiccantNeedsFiltration:
|
|
1126
|
+
type: desiccantNeedsFiltration
|
|
1127
|
+
severity: warning
|
|
1128
|
+
message: A desiccant dryer needs high-efficiency or oil-vapour filtration upstream, or oil aerosol spoils the desiccant.
|
|
1129
|
+
receiverSize:
|
|
1130
|
+
types:
|
|
1131
|
+
- receiverTooSmall
|
|
1132
|
+
expressUnavailable:
|
|
1133
|
+
type: expressUnavailable
|
|
1134
|
+
severity: warning
|
|
1135
|
+
message: Express delivery covers stock frames up to 110 kW. Choose standard delivery for this frame.
|
|
1136
|
+
companyMissing:
|
|
1137
|
+
type: companyMissing
|
|
1138
|
+
severity: warning
|
|
1139
|
+
message: Enter the company the quote is for.
|
|
1140
|
+
emailInvalid:
|
|
1141
|
+
type: emailInvalid
|
|
1142
|
+
severity: warning
|
|
1143
|
+
message: Enter a valid email address for the quote.
|
|
1144
|
+
|
|
1145
|
+
components:
|
|
1146
|
+
Compressor:
|
|
1147
|
+
price:
|
|
1148
|
+
main:
|
|
1149
|
+
price:
|
|
1150
|
+
$read: $total
|
|
1151
|
+
currency: EUR
|
|
1152
|
+
navigationSteps:
|
|
1153
|
+
main:
|
|
1154
|
+
steps:
|
|
1155
|
+
- label: Duty
|
|
1156
|
+
navigationStates:
|
|
1157
|
+
- duty
|
|
1158
|
+
- label: Air treatment
|
|
1159
|
+
navigationStates:
|
|
1160
|
+
- treatment
|
|
1161
|
+
- label: Installation
|
|
1162
|
+
navigationStates:
|
|
1163
|
+
- installation
|
|
1164
|
+
- label: Service and delivery
|
|
1165
|
+
navigationStates:
|
|
1166
|
+
- service
|
|
1167
|
+
- label: Review
|
|
1168
|
+
navigationStates:
|
|
1169
|
+
- review
|
|
1170
|
+
- requested
|
|
1171
|
+
finalizeAction:
|
|
1172
|
+
quote:
|
|
1173
|
+
variant: primary
|
|
1174
|
+
label: Request quote
|
|
1175
|
+
title: Send the configuration to an application engineer for a firm quote
|
|
1176
|
+
icon: PaperPlaneTilt
|
|
1177
|
+
handler: requestQuote
|
|
1178
|
+
|
|
1179
|
+
# Duty
|
|
1180
|
+
genericDimension:
|
|
1181
|
+
flow:
|
|
1182
|
+
value:
|
|
1183
|
+
$bind: flow
|
|
1184
|
+
min: 2
|
|
1185
|
+
max: 40
|
|
1186
|
+
unit: m³/min
|
|
1187
|
+
decimalPlaces: 1
|
|
1188
|
+
attributeSelection:
|
|
1189
|
+
pressure:
|
|
1190
|
+
value:
|
|
1191
|
+
$bind: pressure
|
|
1192
|
+
options:
|
|
1193
|
+
- label: 7.5 bar
|
|
1194
|
+
description: Workshops and general tools
|
|
1195
|
+
value: Bar7
|
|
1196
|
+
- label: 10 bar
|
|
1197
|
+
description: Most production plants
|
|
1198
|
+
value: Bar10
|
|
1199
|
+
- label: 13 bar
|
|
1200
|
+
description: Blow moulding pre-blow, long networks
|
|
1201
|
+
value: Bar13
|
|
1202
|
+
dutyCycle:
|
|
1203
|
+
value:
|
|
1204
|
+
$bind: dutyCycle
|
|
1205
|
+
options:
|
|
1206
|
+
- label: Intermittent
|
|
1207
|
+
description: Up to 60 % load, one or two shifts
|
|
1208
|
+
value: Intermittent
|
|
1209
|
+
- label: Continuous
|
|
1210
|
+
description: Around the clock, heavy-duty package
|
|
1211
|
+
value: Continuous
|
|
1212
|
+
drive:
|
|
1213
|
+
value:
|
|
1214
|
+
$bind: drive
|
|
1215
|
+
options:
|
|
1216
|
+
- label: Fixed speed
|
|
1217
|
+
description: Load and unload control, lowest first cost
|
|
1218
|
+
value: FixedSpeed
|
|
1219
|
+
- label: Variable speed
|
|
1220
|
+
description: Follows demand, about 30 % less energy
|
|
1221
|
+
value: VariableSpeed
|
|
1222
|
+
|
|
1223
|
+
# Air treatment
|
|
1224
|
+
dryer:
|
|
1225
|
+
value:
|
|
1226
|
+
$bind: dryer
|
|
1227
|
+
options:
|
|
1228
|
+
- label: No dryer
|
|
1229
|
+
description: Wet air, for tools that tolerate it
|
|
1230
|
+
value: None
|
|
1231
|
+
- label: Refrigerant
|
|
1232
|
+
description: Pressure dew point +3 °C
|
|
1233
|
+
value: Refrigerant
|
|
1234
|
+
- label: Desiccant
|
|
1235
|
+
description: Pressure dew point below freezing
|
|
1236
|
+
value: Desiccant
|
|
1237
|
+
dewPoint:
|
|
1238
|
+
value:
|
|
1239
|
+
$bind: dewPoint
|
|
1240
|
+
options:
|
|
1241
|
+
- label: −40 °C
|
|
1242
|
+
description: Outdoor lines, food and pharma
|
|
1243
|
+
value: Minus40
|
|
1244
|
+
- label: −70 °C
|
|
1245
|
+
description: Electronics and laboratories
|
|
1246
|
+
value: Minus70
|
|
1247
|
+
$filter:
|
|
1248
|
+
active:
|
|
1249
|
+
$eval: self.dryer === 'Desiccant'
|
|
1250
|
+
filtration:
|
|
1251
|
+
value:
|
|
1252
|
+
$bind: filtration
|
|
1253
|
+
options:
|
|
1254
|
+
- label: General purpose
|
|
1255
|
+
description: Removes particles down to 1 µm
|
|
1256
|
+
value: General
|
|
1257
|
+
- label: High efficiency
|
|
1258
|
+
description: Oil aerosol down to 0.01 mg/m³
|
|
1259
|
+
value: HighEfficiency
|
|
1260
|
+
- label: Oil vapour
|
|
1261
|
+
description: Adds activated carbon, 0.003 mg/m³
|
|
1262
|
+
value: OilVapour
|
|
1263
|
+
|
|
1264
|
+
# Installation
|
|
1265
|
+
cooling:
|
|
1266
|
+
value:
|
|
1267
|
+
$bind: cooling
|
|
1268
|
+
options:
|
|
1269
|
+
- label: Air-cooled
|
|
1270
|
+
description: Needs ventilation in the plant room
|
|
1271
|
+
value: Air
|
|
1272
|
+
- label: Water-cooled
|
|
1273
|
+
description: For hot rooms and heat recovery
|
|
1274
|
+
value: Water
|
|
1275
|
+
receiver:
|
|
1276
|
+
value:
|
|
1277
|
+
$bind: receiver
|
|
1278
|
+
options:
|
|
1279
|
+
- label: No receiver
|
|
1280
|
+
value: None
|
|
1281
|
+
- label: 500 L
|
|
1282
|
+
value: Litres500
|
|
1283
|
+
- label: 1,000 L
|
|
1284
|
+
value: Litres1000
|
|
1285
|
+
- label: 2,000 L
|
|
1286
|
+
value: Litres2000
|
|
1287
|
+
enclosure:
|
|
1288
|
+
value:
|
|
1289
|
+
$bind: enclosure
|
|
1290
|
+
options:
|
|
1291
|
+
- label: Standard canopy
|
|
1292
|
+
description: About 72 dB(A) at 1 m
|
|
1293
|
+
value: Standard
|
|
1294
|
+
- label: Low-noise canopy
|
|
1295
|
+
description: About 66 dB(A) at 1 m
|
|
1296
|
+
value: LowNoise
|
|
1297
|
+
supply:
|
|
1298
|
+
value:
|
|
1299
|
+
$bind: supply
|
|
1300
|
+
options:
|
|
1301
|
+
- label: 400 V, 50 Hz
|
|
1302
|
+
value: V400Hz50
|
|
1303
|
+
- label: 480 V, 60 Hz
|
|
1304
|
+
value: V480Hz60
|
|
1305
|
+
|
|
1306
|
+
# Service and delivery
|
|
1307
|
+
servicePlan:
|
|
1308
|
+
value:
|
|
1309
|
+
$bind: servicePlan
|
|
1310
|
+
options:
|
|
1311
|
+
- label: No plan
|
|
1312
|
+
description: Service on call
|
|
1313
|
+
value: None
|
|
1314
|
+
- label: Essential, 3 years
|
|
1315
|
+
description: Scheduled service and parts
|
|
1316
|
+
value: Essential
|
|
1317
|
+
- label: Total care, 5 years
|
|
1318
|
+
description: Parts, labour and an extended warranty
|
|
1319
|
+
value: TotalCare
|
|
1320
|
+
delivery:
|
|
1321
|
+
value:
|
|
1322
|
+
$bind: delivery
|
|
1323
|
+
options:
|
|
1324
|
+
$read: $deliveryOptions
|
|
1325
|
+
attributeSwitch:
|
|
1326
|
+
heatRecovery:
|
|
1327
|
+
value:
|
|
1328
|
+
$bind: heatRecovery
|
|
1329
|
+
$filter:
|
|
1330
|
+
active:
|
|
1331
|
+
$eval: self.cooling === 'Water'
|
|
1332
|
+
commissioning:
|
|
1333
|
+
value:
|
|
1334
|
+
$bind: commissioning
|
|
1335
|
+
textValue:
|
|
1336
|
+
company:
|
|
1337
|
+
value:
|
|
1338
|
+
$bind: company
|
|
1339
|
+
email:
|
|
1340
|
+
value:
|
|
1341
|
+
$bind: email
|
|
1342
|
+
projectReference:
|
|
1343
|
+
value:
|
|
1344
|
+
$bind: projectReference
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
### ./src/\_form/ChoiceField.tsx
|
|
1348
|
+
|
|
1349
|
+
```tsx
|
|
1350
|
+
import { Box, ButtonBase, Typography } from '@mui/material'
|
|
1351
|
+
import type { KeyboardEvent } from 'react'
|
|
1352
|
+
|
|
1353
|
+
import { attributeSelectionComponent } from '@salsita-ai/sdk/domain'
|
|
1354
|
+
|
|
1355
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
1356
|
+
import { FieldShell } from './FieldShell.js'
|
|
1357
|
+
import { ARDENT } from './tokens.js'
|
|
1358
|
+
|
|
1359
|
+
// Arrow keys move the selection between the cards, as in a native radio group
|
|
1360
|
+
const ARROW_STEPS: Readonly<Record<string, number | undefined>> = {
|
|
1361
|
+
ArrowRight: 1,
|
|
1362
|
+
ArrowDown: 1,
|
|
1363
|
+
ArrowLeft: -1,
|
|
1364
|
+
ArrowUp: -1,
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
interface ChoiceFieldProps {
|
|
1368
|
+
readonly instanceName: string
|
|
1369
|
+
readonly label: string
|
|
1370
|
+
readonly hint?: string
|
|
1371
|
+
readonly warningTypes?: readonly string[]
|
|
1372
|
+
readonly readOnly: boolean
|
|
1373
|
+
// The narrowest a card gets before the cards wrap onto another row
|
|
1374
|
+
readonly minCardWidth?: number
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const ChoiceCards = ({ instanceName, label, hint, warningTypes, readOnly, minCardWidth = 150 }: ChoiceFieldProps) => {
|
|
1378
|
+
const resolve = phx.frontend.useResolveLocalizable()
|
|
1379
|
+
const compressorId = phx.frontend.useRootEntityId()
|
|
1380
|
+
const { value, options } = phx.frontend.useAttributeSelection(compressorId, instanceName)
|
|
1381
|
+
// An option a `fixable` or `fatal` invariant rules out is `disabled`, with the reason in its `hint`
|
|
1382
|
+
const optionHint = options.find(option => option.disabled && option.hint)?.hint
|
|
1383
|
+
|
|
1384
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLElement>, index: number) => {
|
|
1385
|
+
const step = ARROW_STEPS[event.key]
|
|
1386
|
+
|
|
1387
|
+
if (step === undefined) {
|
|
1388
|
+
return
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
event.preventDefault()
|
|
1392
|
+
const next = (index + step + options.length) % options.length
|
|
1393
|
+
options[next]?.onSelect()
|
|
1394
|
+
event.currentTarget.parentElement?.querySelectorAll<HTMLElement>('[role="radio"]')[next]?.focus()
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return (
|
|
1398
|
+
<FieldShell id={`ardent-${instanceName}`} label={label} hint={optionHint ?? hint} warningTypes={warningTypes}>
|
|
1399
|
+
<Box
|
|
1400
|
+
component="div"
|
|
1401
|
+
role="radiogroup"
|
|
1402
|
+
aria-labelledby={`ardent-${instanceName}-label`}
|
|
1403
|
+
sx={{ display: 'grid', gridTemplateColumns: `repeat(auto-fit, minmax(${minCardWidth}px, 1fr))`, gap: 1.25 }}
|
|
1404
|
+
>
|
|
1405
|
+
{options.map((option, index) => {
|
|
1406
|
+
const isSelected = option.value === value
|
|
1407
|
+
const description = resolve(option.description)
|
|
1408
|
+
|
|
1409
|
+
return (
|
|
1410
|
+
<ButtonBase
|
|
1411
|
+
key={String(option.value)}
|
|
1412
|
+
role="radio"
|
|
1413
|
+
aria-checked={isSelected}
|
|
1414
|
+
aria-disabled={option.disabled}
|
|
1415
|
+
tabIndex={isSelected ? 0 : -1}
|
|
1416
|
+
disabled={readOnly && !isSelected}
|
|
1417
|
+
onClick={readOnly ? undefined : option.onSelect}
|
|
1418
|
+
onKeyDown={readOnly ? undefined : event => handleKeyDown(event, index)}
|
|
1419
|
+
sx={{
|
|
1420
|
+
display: 'grid',
|
|
1421
|
+
gridTemplateColumns: 'auto 1fr',
|
|
1422
|
+
alignItems: 'start',
|
|
1423
|
+
columnGap: 1.25,
|
|
1424
|
+
rowGap: 0.25,
|
|
1425
|
+
padding: 1.5,
|
|
1426
|
+
textAlign: 'start',
|
|
1427
|
+
borderRadius: 2,
|
|
1428
|
+
border: `1px solid ${isSelected ? ARDENT.signal : ARDENT.line}`,
|
|
1429
|
+
boxShadow: isSelected ? `inset 0 0 0 1px ${ARDENT.signal}` : 'none',
|
|
1430
|
+
bgcolor: isSelected ? ARDENT.signalTint : ARDENT.panel,
|
|
1431
|
+
fontFamily: ARDENT.font,
|
|
1432
|
+
opacity: option.disabled ? 0.45 : 1,
|
|
1433
|
+
transition: 'border-color 140ms, background-color 140ms',
|
|
1434
|
+
'&:hover': { borderColor: isSelected ? ARDENT.signal : ARDENT.steel },
|
|
1435
|
+
'&.Mui-focusVisible': { outline: `2px solid ${ARDENT.navy}`, outlineOffset: 2 },
|
|
1436
|
+
'&.Mui-disabled': { opacity: 0.45 },
|
|
1437
|
+
}}
|
|
1438
|
+
>
|
|
1439
|
+
{/* The radio mark: a ring, filled with a dot when the card is chosen */}
|
|
1440
|
+
<Box
|
|
1441
|
+
component="span"
|
|
1442
|
+
aria-hidden
|
|
1443
|
+
sx={{
|
|
1444
|
+
gridRow: 'span 2',
|
|
1445
|
+
marginBlockStart: 0.25,
|
|
1446
|
+
inlineSize: 18,
|
|
1447
|
+
blockSize: 18,
|
|
1448
|
+
borderRadius: '50%',
|
|
1449
|
+
border: `2px solid ${isSelected ? ARDENT.signal : ARDENT.steel}`,
|
|
1450
|
+
boxShadow: isSelected ? `inset 0 0 0 3px ${ARDENT.signalTint}` : 'none',
|
|
1451
|
+
bgcolor: isSelected ? ARDENT.signal : 'transparent',
|
|
1452
|
+
}}
|
|
1453
|
+
/>
|
|
1454
|
+
<Typography
|
|
1455
|
+
component="span"
|
|
1456
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, fontWeight: 600, color: ARDENT.ink }}
|
|
1457
|
+
>
|
|
1458
|
+
{resolve(option.title)}
|
|
1459
|
+
</Typography>
|
|
1460
|
+
{description && (
|
|
1461
|
+
<Typography
|
|
1462
|
+
component="span"
|
|
1463
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 13, lineHeight: 1.35, color: ARDENT.steel }}
|
|
1464
|
+
>
|
|
1465
|
+
{description}
|
|
1466
|
+
</Typography>
|
|
1467
|
+
)}
|
|
1468
|
+
</ButtonBase>
|
|
1469
|
+
)
|
|
1470
|
+
})}
|
|
1471
|
+
</Box>
|
|
1472
|
+
</FieldShell>
|
|
1473
|
+
)
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// One `attributeSelection` of the spec as a group of cards. The field exists only while the spec's component is
|
|
1477
|
+
// active: a `$filter` in the spec decides when a question is asked, and the layout follows it. The control hook throws
|
|
1478
|
+
// for an inactive component, so the cards mount only after `useComponent` has found it. An option that a `fixable`
|
|
1479
|
+
// invariant rules out is faded with its hint below the cards; it stays clickable, the change is rejected and the
|
|
1480
|
+
// built-in snackbar says why.
|
|
1481
|
+
export const ChoiceField = (props: ChoiceFieldProps) => {
|
|
1482
|
+
const compressorId = phx.frontend.useRootEntityId()
|
|
1483
|
+
const component = phx.frontend.useComponent({
|
|
1484
|
+
entityId: compressorId,
|
|
1485
|
+
componentType: attributeSelectionComponent,
|
|
1486
|
+
instanceName: props.instanceName,
|
|
1487
|
+
})
|
|
1488
|
+
|
|
1489
|
+
return component === null ? null : <ChoiceCards {...props} />
|
|
1490
|
+
}
|
|
1491
|
+
```
|
|
1492
|
+
|
|
1493
|
+
### ./src/\_form/FieldShell.tsx
|
|
1494
|
+
|
|
1495
|
+
```tsx
|
|
1496
|
+
import { Box, Typography } from '@mui/material'
|
|
1497
|
+
import type { ReactNode } from 'react'
|
|
1498
|
+
|
|
1499
|
+
import { ARDENT } from './tokens.js'
|
|
1500
|
+
import { useWarnings } from './use-warnings.js'
|
|
1501
|
+
|
|
1502
|
+
const WARNING_ICON = 'M12 3 2 21h20L12 3Zm0 6v5m0 3.5v.5'
|
|
1503
|
+
|
|
1504
|
+
export interface FieldShellProps {
|
|
1505
|
+
readonly id: string
|
|
1506
|
+
readonly label: string
|
|
1507
|
+
// Help shown below the input while the field has no warning
|
|
1508
|
+
readonly hint?: string
|
|
1509
|
+
// The invariant types whose warning belongs to this field
|
|
1510
|
+
readonly warningTypes?: readonly string[]
|
|
1511
|
+
// Hides the warning until the customer has had a chance to fill the field in
|
|
1512
|
+
readonly showWarning?: boolean
|
|
1513
|
+
readonly aside?: ReactNode
|
|
1514
|
+
readonly children: ReactNode
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// A field's frame: the label, the input, and below it either the help text or the warning of an invariant that the
|
|
1518
|
+
// field can resolve. The warning comes from the committed configuration, so it appears and disappears with the rule.
|
|
1519
|
+
export const FieldShell = ({
|
|
1520
|
+
id,
|
|
1521
|
+
label,
|
|
1522
|
+
hint,
|
|
1523
|
+
warningTypes = [],
|
|
1524
|
+
showWarning = true,
|
|
1525
|
+
aside,
|
|
1526
|
+
children,
|
|
1527
|
+
}: FieldShellProps) => {
|
|
1528
|
+
const warning = useWarnings().find(candidate => warningTypes.includes(candidate.type))
|
|
1529
|
+
const visibleWarning = showWarning ? warning : undefined
|
|
1530
|
+
|
|
1531
|
+
return (
|
|
1532
|
+
<Box
|
|
1533
|
+
component="div"
|
|
1534
|
+
role="group"
|
|
1535
|
+
aria-labelledby={`${id}-label`}
|
|
1536
|
+
sx={{
|
|
1537
|
+
display: 'grid',
|
|
1538
|
+
gap: 1.25,
|
|
1539
|
+
animation: 'ardent-field-in 220ms ease-out',
|
|
1540
|
+
'@keyframes ardent-field-in': {
|
|
1541
|
+
from: { opacity: 0, transform: 'translateY(-4px)' },
|
|
1542
|
+
to: { opacity: 1, transform: 'none' },
|
|
1543
|
+
},
|
|
1544
|
+
}}
|
|
1545
|
+
>
|
|
1546
|
+
<Box component="div" sx={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 2 }}>
|
|
1547
|
+
<Typography
|
|
1548
|
+
id={`${id}-label`}
|
|
1549
|
+
component="h4"
|
|
1550
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 16, fontWeight: 600, color: ARDENT.ink }}
|
|
1551
|
+
>
|
|
1552
|
+
{label}
|
|
1553
|
+
</Typography>
|
|
1554
|
+
{aside}
|
|
1555
|
+
</Box>
|
|
1556
|
+
{children}
|
|
1557
|
+
{visibleWarning ? (
|
|
1558
|
+
<Box
|
|
1559
|
+
component="p"
|
|
1560
|
+
role="alert"
|
|
1561
|
+
sx={{
|
|
1562
|
+
display: 'flex',
|
|
1563
|
+
gap: 1,
|
|
1564
|
+
alignItems: 'flex-start',
|
|
1565
|
+
margin: 0,
|
|
1566
|
+
paddingBlock: 1,
|
|
1567
|
+
paddingInline: 1.5,
|
|
1568
|
+
borderRadius: 1.5,
|
|
1569
|
+
bgcolor: ARDENT.warningTint,
|
|
1570
|
+
color: ARDENT.warning,
|
|
1571
|
+
fontFamily: ARDENT.font,
|
|
1572
|
+
fontSize: 14,
|
|
1573
|
+
lineHeight: 1.4,
|
|
1574
|
+
}}
|
|
1575
|
+
>
|
|
1576
|
+
<Box component="svg" viewBox="0 0 24 24" aria-hidden sx={{ inlineSize: 18, blockSize: 18, flex: 'none' }}>
|
|
1577
|
+
<path d={WARNING_ICON} fill="none" stroke="currentColor" strokeWidth={2} strokeLinejoin="round" />
|
|
1578
|
+
</Box>
|
|
1579
|
+
{visibleWarning.text}
|
|
1580
|
+
</Box>
|
|
1581
|
+
) : (
|
|
1582
|
+
hint && <Typography sx={{ fontFamily: ARDENT.font, fontSize: 14, color: ARDENT.steel }}>{hint}</Typography>
|
|
1583
|
+
)}
|
|
1584
|
+
</Box>
|
|
1585
|
+
)
|
|
1586
|
+
}
|
|
1587
|
+
```
|
|
1588
|
+
|
|
1589
|
+
### ./src/\_form/NumberField.tsx
|
|
1590
|
+
|
|
1591
|
+
```tsx
|
|
1592
|
+
import { Box, InputBase, Slider, Typography } from '@mui/material'
|
|
1593
|
+
import { useEffect, useState } from 'react'
|
|
1594
|
+
|
|
1595
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
1596
|
+
import { FieldShell } from './FieldShell.js'
|
|
1597
|
+
import { ARDENT } from './tokens.js'
|
|
1598
|
+
|
|
1599
|
+
interface NumberFieldProps {
|
|
1600
|
+
readonly instanceName: string
|
|
1601
|
+
readonly label: string
|
|
1602
|
+
readonly hint?: string
|
|
1603
|
+
readonly warningTypes?: readonly string[]
|
|
1604
|
+
readonly readOnly: boolean
|
|
1605
|
+
readonly step: number
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// One `genericDimension` of the spec as a slider with a typed value beside it. Dragging stages each value, so the
|
|
1609
|
+
// price follows the thumb, and letting go commits it; a typed value is committed on Enter or when the input loses
|
|
1610
|
+
// focus. The slider's range is the component's `min` and `max`, which the spec can read from expressions.
|
|
1611
|
+
export const NumberField = ({ instanceName, label, hint, warningTypes, readOnly, step }: NumberFieldProps) => {
|
|
1612
|
+
const compressorId = phx.frontend.useRootEntityId()
|
|
1613
|
+
const dimension = phx.frontend.useGenericDimension(compressorId, instanceName)
|
|
1614
|
+
const [draft, setDraft] = useState(String(dimension.value))
|
|
1615
|
+
const min = dimension.min ?? 0
|
|
1616
|
+
const max = dimension.max ?? 100
|
|
1617
|
+
|
|
1618
|
+
useEffect(() => {
|
|
1619
|
+
setDraft(String(dimension.value))
|
|
1620
|
+
}, [dimension.value])
|
|
1621
|
+
|
|
1622
|
+
const commitDraft = () => {
|
|
1623
|
+
const parsed = Number(draft.replace(',', '.'))
|
|
1624
|
+
|
|
1625
|
+
if (!Number.isFinite(parsed)) {
|
|
1626
|
+
setDraft(String(dimension.value))
|
|
1627
|
+
|
|
1628
|
+
return
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
dimension.update(parsed)
|
|
1632
|
+
dimension.resolve()
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
return (
|
|
1636
|
+
<FieldShell
|
|
1637
|
+
id={`ardent-${instanceName}`}
|
|
1638
|
+
label={label}
|
|
1639
|
+
hint={hint}
|
|
1640
|
+
warningTypes={warningTypes}
|
|
1641
|
+
aside={
|
|
1642
|
+
<Box
|
|
1643
|
+
component="label"
|
|
1644
|
+
sx={{
|
|
1645
|
+
display: 'flex',
|
|
1646
|
+
alignItems: 'baseline',
|
|
1647
|
+
gap: 0.75,
|
|
1648
|
+
paddingInline: 1.25,
|
|
1649
|
+
paddingBlock: 0.5,
|
|
1650
|
+
borderRadius: 1.5,
|
|
1651
|
+
border: `1px solid ${ARDENT.line}`,
|
|
1652
|
+
bgcolor: ARDENT.panel,
|
|
1653
|
+
'&:focus-within': { borderColor: ARDENT.navy },
|
|
1654
|
+
}}
|
|
1655
|
+
>
|
|
1656
|
+
<InputBase
|
|
1657
|
+
value={draft}
|
|
1658
|
+
disabled={readOnly}
|
|
1659
|
+
onChange={event => setDraft(event.target.value)}
|
|
1660
|
+
onBlur={commitDraft}
|
|
1661
|
+
onKeyDown={event => {
|
|
1662
|
+
if (event.key === 'Enter') {
|
|
1663
|
+
commitDraft()
|
|
1664
|
+
}
|
|
1665
|
+
}}
|
|
1666
|
+
inputProps={{ inputMode: 'decimal', 'aria-label': `${label}, ${dimension.unit}` }}
|
|
1667
|
+
sx={{
|
|
1668
|
+
inlineSize: 56,
|
|
1669
|
+
fontFamily: ARDENT.font,
|
|
1670
|
+
fontSize: 20,
|
|
1671
|
+
fontWeight: 600,
|
|
1672
|
+
color: ARDENT.ink,
|
|
1673
|
+
'& input': { padding: 0, textAlign: 'end' },
|
|
1674
|
+
}}
|
|
1675
|
+
/>
|
|
1676
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 14, color: ARDENT.steel }}>
|
|
1677
|
+
{dimension.unit}
|
|
1678
|
+
</Typography>
|
|
1679
|
+
</Box>
|
|
1680
|
+
}
|
|
1681
|
+
>
|
|
1682
|
+
<Box component="div" sx={{ paddingInline: 1.25 }}>
|
|
1683
|
+
<Slider
|
|
1684
|
+
value={Math.min(dimension.value, max)}
|
|
1685
|
+
min={min}
|
|
1686
|
+
max={max}
|
|
1687
|
+
step={step}
|
|
1688
|
+
disabled={readOnly}
|
|
1689
|
+
onChange={(_, value) => dimension.update(Array.isArray(value) ? (value[0] ?? min) : value)}
|
|
1690
|
+
onChangeCommitted={() => dimension.resolve()}
|
|
1691
|
+
marks={[
|
|
1692
|
+
{ value: min, label: `${min}` },
|
|
1693
|
+
{ value: max, label: `${max}` },
|
|
1694
|
+
]}
|
|
1695
|
+
slotProps={{ input: { 'aria-label': label } }}
|
|
1696
|
+
sx={{
|
|
1697
|
+
color: ARDENT.signal,
|
|
1698
|
+
blockSize: 6,
|
|
1699
|
+
'& .MuiSlider-rail': { bgcolor: ARDENT.line, opacity: 1 },
|
|
1700
|
+
'& .MuiSlider-thumb': {
|
|
1701
|
+
inlineSize: 22,
|
|
1702
|
+
blockSize: 22,
|
|
1703
|
+
bgcolor: ARDENT.panel,
|
|
1704
|
+
border: `3px solid ${ARDENT.signal}`,
|
|
1705
|
+
},
|
|
1706
|
+
'& .MuiSlider-markLabel': { fontFamily: ARDENT.font, fontSize: 12, color: ARDENT.steel },
|
|
1707
|
+
'& .MuiSlider-mark': { display: 'none' },
|
|
1708
|
+
}}
|
|
1709
|
+
/>
|
|
1710
|
+
</Box>
|
|
1711
|
+
</FieldShell>
|
|
1712
|
+
)
|
|
1713
|
+
}
|
|
1714
|
+
```
|
|
1715
|
+
|
|
1716
|
+
### ./src/\_form/SwitchField.tsx
|
|
1717
|
+
|
|
1718
|
+
```tsx
|
|
1719
|
+
import { Box, Switch, Typography } from '@mui/material'
|
|
1720
|
+
|
|
1721
|
+
import { attributeSwitchComponent } from '@salsita-ai/sdk/domain'
|
|
1722
|
+
|
|
1723
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
1724
|
+
import { ARDENT } from './tokens.js'
|
|
1725
|
+
|
|
1726
|
+
interface SwitchFieldProps {
|
|
1727
|
+
readonly instanceName: string
|
|
1728
|
+
readonly label: string
|
|
1729
|
+
readonly description: string
|
|
1730
|
+
readonly readOnly: boolean
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
const SwitchRow = ({ instanceName, label, description, readOnly }: SwitchFieldProps) => {
|
|
1734
|
+
const compressorId = phx.frontend.useRootEntityId()
|
|
1735
|
+
const toggle = phx.frontend.useAttributeSwitch(compressorId, instanceName)
|
|
1736
|
+
const labelId = `ardent-${instanceName}-label`
|
|
1737
|
+
|
|
1738
|
+
return (
|
|
1739
|
+
<Box
|
|
1740
|
+
component="div"
|
|
1741
|
+
sx={{
|
|
1742
|
+
display: 'flex',
|
|
1743
|
+
alignItems: 'center',
|
|
1744
|
+
justifyContent: 'space-between',
|
|
1745
|
+
gap: 2,
|
|
1746
|
+
padding: 1.5,
|
|
1747
|
+
borderRadius: 2,
|
|
1748
|
+
border: `1px solid ${ARDENT.line}`,
|
|
1749
|
+
animation: 'ardent-switch-in 220ms ease-out',
|
|
1750
|
+
'@keyframes ardent-switch-in': { from: { opacity: 0 }, to: { opacity: 1 } },
|
|
1751
|
+
}}
|
|
1752
|
+
>
|
|
1753
|
+
<Box component="div" sx={{ display: 'grid', gap: 0.25 }}>
|
|
1754
|
+
<Typography id={labelId} sx={{ fontFamily: ARDENT.font, fontSize: 16, fontWeight: 600, color: ARDENT.ink }}>
|
|
1755
|
+
{label}
|
|
1756
|
+
</Typography>
|
|
1757
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 14, color: ARDENT.steel }}>{description}</Typography>
|
|
1758
|
+
</Box>
|
|
1759
|
+
<Switch
|
|
1760
|
+
checked={toggle.state.value}
|
|
1761
|
+
disabled={readOnly || toggle.disabled}
|
|
1762
|
+
onChange={toggle.onChange}
|
|
1763
|
+
inputProps={{ 'aria-labelledby': labelId }}
|
|
1764
|
+
sx={{
|
|
1765
|
+
'& .MuiSwitch-switchBase.Mui-checked': { color: ARDENT.panel },
|
|
1766
|
+
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: ARDENT.signal, opacity: 1 },
|
|
1767
|
+
'& .MuiSwitch-track': { bgcolor: ARDENT.steel, opacity: 0.5 },
|
|
1768
|
+
}}
|
|
1769
|
+
/>
|
|
1770
|
+
</Box>
|
|
1771
|
+
)
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// One `attributeSwitch` of the spec as a labelled switch, shown only while the spec's component is active
|
|
1775
|
+
export const SwitchField = (props: SwitchFieldProps) => {
|
|
1776
|
+
const compressorId = phx.frontend.useRootEntityId()
|
|
1777
|
+
const component = phx.frontend.useComponent({
|
|
1778
|
+
entityId: compressorId,
|
|
1779
|
+
componentType: attributeSwitchComponent,
|
|
1780
|
+
instanceName: props.instanceName,
|
|
1781
|
+
})
|
|
1782
|
+
|
|
1783
|
+
return component === null ? null : <SwitchRow {...props} />
|
|
1784
|
+
}
|
|
1785
|
+
```
|
|
1786
|
+
|
|
1787
|
+
### ./src/\_form/TextInputField.tsx
|
|
1788
|
+
|
|
1789
|
+
```tsx
|
|
1790
|
+
import { InputBase } from '@mui/material'
|
|
1791
|
+
import { useState } from 'react'
|
|
1792
|
+
|
|
1793
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
1794
|
+
import { FieldShell } from './FieldShell.js'
|
|
1795
|
+
import { ARDENT } from './tokens.js'
|
|
1796
|
+
|
|
1797
|
+
interface TextInputFieldProps {
|
|
1798
|
+
readonly instanceName: string
|
|
1799
|
+
readonly label: string
|
|
1800
|
+
readonly hint?: string
|
|
1801
|
+
readonly warningTypes?: readonly string[]
|
|
1802
|
+
readonly readOnly: boolean
|
|
1803
|
+
readonly type?: 'text' | 'email'
|
|
1804
|
+
readonly autoComplete?: string
|
|
1805
|
+
// Shows the field's warning before the customer has left the field, after a request was attempted
|
|
1806
|
+
readonly revealWarning: boolean
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// One `textValue` of the spec as a text input. Typing stages the text and leaving the field commits it, so the
|
|
1810
|
+
// invariants judge the committed text; their warning is shown once the customer has left the field.
|
|
1811
|
+
export const TextInputField = ({
|
|
1812
|
+
instanceName,
|
|
1813
|
+
label,
|
|
1814
|
+
hint,
|
|
1815
|
+
warningTypes,
|
|
1816
|
+
readOnly,
|
|
1817
|
+
type = 'text',
|
|
1818
|
+
autoComplete,
|
|
1819
|
+
revealWarning,
|
|
1820
|
+
}: TextInputFieldProps) => {
|
|
1821
|
+
const compressorId = phx.frontend.useRootEntityId()
|
|
1822
|
+
const text = phx.frontend.useTextValue(compressorId, instanceName)
|
|
1823
|
+
const [touched, setTouched] = useState(false)
|
|
1824
|
+
|
|
1825
|
+
return (
|
|
1826
|
+
<FieldShell
|
|
1827
|
+
id={`ardent-${instanceName}`}
|
|
1828
|
+
label={label}
|
|
1829
|
+
hint={hint}
|
|
1830
|
+
warningTypes={warningTypes}
|
|
1831
|
+
showWarning={touched || revealWarning}
|
|
1832
|
+
>
|
|
1833
|
+
<InputBase
|
|
1834
|
+
value={text.value}
|
|
1835
|
+
type={type}
|
|
1836
|
+
disabled={readOnly}
|
|
1837
|
+
autoComplete={autoComplete}
|
|
1838
|
+
onChange={event => text.update(event, event.target.value)}
|
|
1839
|
+
onBlur={() => {
|
|
1840
|
+
text.resolve()
|
|
1841
|
+
setTouched(true)
|
|
1842
|
+
}}
|
|
1843
|
+
inputProps={{ 'aria-labelledby': `ardent-${instanceName}-label` }}
|
|
1844
|
+
sx={{
|
|
1845
|
+
paddingInline: 1.5,
|
|
1846
|
+
paddingBlock: 1,
|
|
1847
|
+
borderRadius: 2,
|
|
1848
|
+
border: `1px solid ${ARDENT.line}`,
|
|
1849
|
+
bgcolor: ARDENT.panel,
|
|
1850
|
+
fontFamily: ARDENT.font,
|
|
1851
|
+
fontSize: 16,
|
|
1852
|
+
color: ARDENT.ink,
|
|
1853
|
+
'&.Mui-focused': { borderColor: ARDENT.navy, boxShadow: `0 0 0 3px ${ARDENT.page}` },
|
|
1854
|
+
}}
|
|
1855
|
+
/>
|
|
1856
|
+
</FieldShell>
|
|
1857
|
+
)
|
|
1858
|
+
}
|
|
1859
|
+
```
|
|
1860
|
+
|
|
1861
|
+
### ./src/\_form/tokens.ts
|
|
1862
|
+
|
|
1863
|
+
```ts
|
|
1864
|
+
// Ardent Air's palette and type: a navy shell, white work surfaces and the signal orange of the machines' doors.
|
|
1865
|
+
// Barlow is the brand's typeface; the stack falls back to system fonts where it is not loaded.
|
|
1866
|
+
export const ARDENT = {
|
|
1867
|
+
navy: '#0d1b2e',
|
|
1868
|
+
navySoft: '#1b2c45',
|
|
1869
|
+
ink: '#15202e',
|
|
1870
|
+
steel: '#5a6778',
|
|
1871
|
+
line: '#dde2e9',
|
|
1872
|
+
page: '#f1f3f6',
|
|
1873
|
+
panel: '#ffffff',
|
|
1874
|
+
signal: '#f0641a',
|
|
1875
|
+
signalDark: '#c84f0e',
|
|
1876
|
+
signalTint: '#fff1e8',
|
|
1877
|
+
warning: '#9a4b00',
|
|
1878
|
+
warningTint: '#fff6e0',
|
|
1879
|
+
success: '#17703f',
|
|
1880
|
+
successTint: '#e9f6ee',
|
|
1881
|
+
font: "'Barlow', 'Helvetica Neue', Arial, sans-serif",
|
|
1882
|
+
} as const
|
|
1883
|
+
|
|
1884
|
+
// Small capitals with wide tracking, for section labels and table headings
|
|
1885
|
+
export const OVERLINE_SX = {
|
|
1886
|
+
fontFamily: ARDENT.font,
|
|
1887
|
+
fontSize: 12,
|
|
1888
|
+
fontWeight: 500,
|
|
1889
|
+
letterSpacing: '0.14em',
|
|
1890
|
+
textTransform: 'uppercase',
|
|
1891
|
+
color: ARDENT.steel,
|
|
1892
|
+
} as const
|
|
1893
|
+
```
|
|
1894
|
+
|
|
1895
|
+
### ./src/\_form/use-warnings.ts
|
|
1896
|
+
|
|
1897
|
+
```ts
|
|
1898
|
+
import { useMemo } from 'react'
|
|
1899
|
+
|
|
1900
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
1901
|
+
|
|
1902
|
+
export interface Warning {
|
|
1903
|
+
readonly type: string
|
|
1904
|
+
readonly text: string
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
// The warnings of the committed configuration with their text resolved. A `warning` invariant keeps the change, so its
|
|
1908
|
+
// violation stays in the committed state until the customer resolves it, and finalizing waits for that.
|
|
1909
|
+
// `useFinalizeViolations` lists them; `useStateValidity` holds the same list, but on `phx.frontend` it is typed as the
|
|
1910
|
+
// validity value instead of a hook, so calling it does not type-check.
|
|
1911
|
+
export const useWarnings = (): readonly Warning[] => {
|
|
1912
|
+
const { violations } = phx.frontend.useFinalizeViolations()
|
|
1913
|
+
const resolve = phx.frontend.useResolveLocalizable()
|
|
1914
|
+
|
|
1915
|
+
return useMemo(() => {
|
|
1916
|
+
return (violations ?? []).map(({ violation }) => ({ type: violation.type, text: resolve(violation.message) ?? '' }))
|
|
1917
|
+
}, [violations, resolve])
|
|
1918
|
+
}
|
|
1919
|
+
```
|
|
1920
|
+
|
|
1921
|
+
### ./src/\_shared/DesktopPage.tsx
|
|
1922
|
+
|
|
1923
|
+
```tsx
|
|
1924
|
+
import { Box, Button, IconButton, Tooltip, Typography } from '@mui/material'
|
|
1925
|
+
import { useEffect, useRef, useState } from 'react'
|
|
1926
|
+
|
|
1927
|
+
import { LayoutSlots } from '@salsita-ai/sdk/configurator'
|
|
1928
|
+
|
|
1929
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
1930
|
+
import { ARDENT, useWarnings } from '../_form/index.js'
|
|
1931
|
+
import { PriceBreakdown } from './PriceBreakdown.js'
|
|
1932
|
+
import { QuoteAction } from './QuoteAction.js'
|
|
1933
|
+
import { StepForm } from './StepForm.js'
|
|
1934
|
+
import { StepHeader } from './StepHeader.js'
|
|
1935
|
+
import { STEP_WARNINGS } from './step-warnings.js'
|
|
1936
|
+
|
|
1937
|
+
const UNDO_ICON = 'M9 14 4 9l5-5M4 9h10.5a5.5 5.5 0 0 1 0 11H11'
|
|
1938
|
+
const REDO_ICON = 'M15 14l5-5-5-5M20 9H9.5a5.5 5.5 0 0 0 0 11H13'
|
|
1939
|
+
const PANEL_WIDTH = 400
|
|
1940
|
+
|
|
1941
|
+
interface IconProps {
|
|
1942
|
+
readonly path: string
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
const Icon = ({ path }: IconProps) => {
|
|
1946
|
+
return (
|
|
1947
|
+
<Box component="svg" viewBox="0 0 24 24" aria-hidden sx={{ inlineSize: 20, blockSize: 20 }}>
|
|
1948
|
+
<path d={path} fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
|
1949
|
+
</Box>
|
|
1950
|
+
)
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// The desktop page: the brand bar with undo and redo, the stepper, the active step's form, and a column with the live
|
|
1954
|
+
// price breakdown and the way on. The form scrolls; the column stays.
|
|
1955
|
+
export const DesktopPage = () => {
|
|
1956
|
+
const { accessInfo } = phx.frontend.useAccessService()
|
|
1957
|
+
const { canUndo, canRedo, undo, redo } = phx.frontend.useUndoRedo()
|
|
1958
|
+
const { activeStep, isLast, canGoPrevious, canGoNext, steps, next, previous, goTo } = phx.frontend.useStepNavigation()
|
|
1959
|
+
const navigationState = phx.frontend.useActiveNavigationState()
|
|
1960
|
+
const compressor = phx.frontend.useRootEntityState()
|
|
1961
|
+
const warnings = useWarnings()
|
|
1962
|
+
const [revealWarnings, setRevealWarnings] = useState(false)
|
|
1963
|
+
const formRef = useRef<HTMLElement>(null)
|
|
1964
|
+
|
|
1965
|
+
// Each step starts at its top, as a new page would
|
|
1966
|
+
useEffect(() => {
|
|
1967
|
+
formRef.current?.scrollTo({ top: 0 })
|
|
1968
|
+
}, [navigationState])
|
|
1969
|
+
const readOnly = accessInfo.viewMode
|
|
1970
|
+
const nextStep = steps[(activeStep?.index ?? -1) + 1]
|
|
1971
|
+
const conflictCount = warnings.filter(warning =>
|
|
1972
|
+
Object.values(STEP_WARNINGS).some(types => types.includes(warning.type))
|
|
1973
|
+
).length
|
|
1974
|
+
|
|
1975
|
+
return (
|
|
1976
|
+
<Box
|
|
1977
|
+
component="div"
|
|
1978
|
+
sx={{
|
|
1979
|
+
position: 'absolute',
|
|
1980
|
+
inset: 0,
|
|
1981
|
+
display: 'grid',
|
|
1982
|
+
gridTemplateRows: 'auto auto minmax(0, 1fr)',
|
|
1983
|
+
gridTemplateColumns: `minmax(0, 1fr) ${PANEL_WIDTH}px`,
|
|
1984
|
+
bgcolor: ARDENT.page,
|
|
1985
|
+
fontFamily: ARDENT.font,
|
|
1986
|
+
}}
|
|
1987
|
+
>
|
|
1988
|
+
<Box
|
|
1989
|
+
component="header"
|
|
1990
|
+
sx={{
|
|
1991
|
+
gridColumn: '1 / -1',
|
|
1992
|
+
display: 'flex',
|
|
1993
|
+
alignItems: 'center',
|
|
1994
|
+
gap: 3,
|
|
1995
|
+
paddingInline: 4,
|
|
1996
|
+
blockSize: 64,
|
|
1997
|
+
bgcolor: ARDENT.navy,
|
|
1998
|
+
color: ARDENT.panel,
|
|
1999
|
+
}}
|
|
2000
|
+
>
|
|
2001
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 22, fontWeight: 700, letterSpacing: '0.12em' }}>
|
|
2002
|
+
ARDENT
|
|
2003
|
+
<Box component="span" sx={{ color: ARDENT.signal }}>
|
|
2004
|
+
{' '}
|
|
2005
|
+
AIR
|
|
2006
|
+
</Box>
|
|
2007
|
+
</Typography>
|
|
2008
|
+
<Box component="span" aria-hidden sx={{ inlineSize: 1, blockSize: 28, bgcolor: ARDENT.navySoft }} />
|
|
2009
|
+
<Typography component="h1" sx={{ fontFamily: ARDENT.font, fontSize: 18, fontWeight: 500 }}>
|
|
2010
|
+
Compressor quote
|
|
2011
|
+
</Typography>
|
|
2012
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 15, color: '#9fb0c6' }}>
|
|
2013
|
+
{compressor.$frameName} · {compressor.$frameKw} kW
|
|
2014
|
+
</Typography>
|
|
2015
|
+
<Box component="div" sx={{ marginInlineStart: 'auto', display: 'flex', gap: 0.5 }}>
|
|
2016
|
+
<Tooltip title="Undo">
|
|
2017
|
+
<span>
|
|
2018
|
+
<IconButton
|
|
2019
|
+
aria-label="Undo"
|
|
2020
|
+
disabled={!canUndo || readOnly}
|
|
2021
|
+
onClick={undo}
|
|
2022
|
+
sx={{ color: ARDENT.panel, '&.Mui-disabled': { color: ARDENT.navySoft } }}
|
|
2023
|
+
>
|
|
2024
|
+
<Icon path={UNDO_ICON} />
|
|
2025
|
+
</IconButton>
|
|
2026
|
+
</span>
|
|
2027
|
+
</Tooltip>
|
|
2028
|
+
<Tooltip title="Redo">
|
|
2029
|
+
<span>
|
|
2030
|
+
<IconButton
|
|
2031
|
+
aria-label="Redo"
|
|
2032
|
+
disabled={!canRedo || readOnly}
|
|
2033
|
+
onClick={redo}
|
|
2034
|
+
sx={{ color: ARDENT.panel, '&.Mui-disabled': { color: ARDENT.navySoft } }}
|
|
2035
|
+
>
|
|
2036
|
+
<Icon path={REDO_ICON} />
|
|
2037
|
+
</IconButton>
|
|
2038
|
+
</span>
|
|
2039
|
+
</Tooltip>
|
|
2040
|
+
</Box>
|
|
2041
|
+
</Box>
|
|
2042
|
+
|
|
2043
|
+
<Box
|
|
2044
|
+
component="div"
|
|
2045
|
+
sx={{ gridColumn: '1 / -1', bgcolor: ARDENT.panel, borderBlockEnd: `1px solid ${ARDENT.line}` }}
|
|
2046
|
+
>
|
|
2047
|
+
<StepHeader compact={false} />
|
|
2048
|
+
</Box>
|
|
2049
|
+
|
|
2050
|
+
<Box component="main" ref={formRef} sx={{ overflowY: 'auto', minBlockSize: 0 }}>
|
|
2051
|
+
<Box component="div" sx={{ display: 'grid', gap: 3, maxInlineSize: 820, marginInline: 'auto', padding: 4 }}>
|
|
2052
|
+
<Box component="div" sx={{ display: 'grid', gap: 0.5 }}>
|
|
2053
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 14, color: ARDENT.steel }}>
|
|
2054
|
+
Step {(activeStep?.index ?? 0) + 1} of {steps.length}
|
|
2055
|
+
</Typography>
|
|
2056
|
+
<Typography
|
|
2057
|
+
component="h2"
|
|
2058
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 32, fontWeight: 700, color: ARDENT.ink }}
|
|
2059
|
+
>
|
|
2060
|
+
{activeStep?.label}
|
|
2061
|
+
</Typography>
|
|
2062
|
+
</Box>
|
|
2063
|
+
<StepForm readOnly={readOnly} revealWarnings={revealWarnings} />
|
|
2064
|
+
<Box
|
|
2065
|
+
component="nav"
|
|
2066
|
+
aria-label="Step navigation"
|
|
2067
|
+
sx={{ display: 'flex', justifyContent: 'space-between', gap: 2 }}
|
|
2068
|
+
>
|
|
2069
|
+
<Button
|
|
2070
|
+
disabled={!canGoPrevious}
|
|
2071
|
+
onClick={previous}
|
|
2072
|
+
sx={{
|
|
2073
|
+
color: ARDENT.ink,
|
|
2074
|
+
fontFamily: ARDENT.font,
|
|
2075
|
+
fontSize: 16,
|
|
2076
|
+
textTransform: 'none',
|
|
2077
|
+
visibility: canGoPrevious ? 'visible' : 'hidden',
|
|
2078
|
+
}}
|
|
2079
|
+
>
|
|
2080
|
+
← Back
|
|
2081
|
+
</Button>
|
|
2082
|
+
{canGoNext && !isLast && (
|
|
2083
|
+
<Button
|
|
2084
|
+
variant="contained"
|
|
2085
|
+
disableElevation
|
|
2086
|
+
onClick={next}
|
|
2087
|
+
sx={{
|
|
2088
|
+
borderRadius: 2,
|
|
2089
|
+
paddingInline: 3,
|
|
2090
|
+
bgcolor: ARDENT.navy,
|
|
2091
|
+
fontFamily: ARDENT.font,
|
|
2092
|
+
fontSize: 16,
|
|
2093
|
+
fontWeight: 600,
|
|
2094
|
+
textTransform: 'none',
|
|
2095
|
+
'&:hover': { bgcolor: ARDENT.ink },
|
|
2096
|
+
}}
|
|
2097
|
+
>
|
|
2098
|
+
Next: {nextStep?.label}
|
|
2099
|
+
</Button>
|
|
2100
|
+
)}
|
|
2101
|
+
</Box>
|
|
2102
|
+
</Box>
|
|
2103
|
+
</Box>
|
|
2104
|
+
|
|
2105
|
+
<Box
|
|
2106
|
+
component="aside"
|
|
2107
|
+
aria-label="Quote"
|
|
2108
|
+
sx={{
|
|
2109
|
+
display: 'flex',
|
|
2110
|
+
flexDirection: 'column',
|
|
2111
|
+
gap: 3,
|
|
2112
|
+
minBlockSize: 0,
|
|
2113
|
+
overflowY: 'auto',
|
|
2114
|
+
padding: 4,
|
|
2115
|
+
bgcolor: ARDENT.panel,
|
|
2116
|
+
borderInlineStart: `1px solid ${ARDENT.line}`,
|
|
2117
|
+
}}
|
|
2118
|
+
>
|
|
2119
|
+
<PriceBreakdown />
|
|
2120
|
+
{isLast ? (
|
|
2121
|
+
navigationState !== 'requested' && <QuoteAction onBlocked={() => setRevealWarnings(true)} compact={false} />
|
|
2122
|
+
) : (
|
|
2123
|
+
<Box component="div" sx={{ display: 'grid', gap: 1 }}>
|
|
2124
|
+
{conflictCount > 0 && (
|
|
2125
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 14, color: ARDENT.warning }}>
|
|
2126
|
+
{conflictCount === 1 ? 'One choice needs attention' : `${conflictCount} choices need attention`} before
|
|
2127
|
+
the quote can be requested.
|
|
2128
|
+
</Typography>
|
|
2129
|
+
)}
|
|
2130
|
+
<Button
|
|
2131
|
+
variant="outlined"
|
|
2132
|
+
onClick={() => goTo('review')}
|
|
2133
|
+
sx={{
|
|
2134
|
+
minBlockSize: 48,
|
|
2135
|
+
borderRadius: 2,
|
|
2136
|
+
borderColor: ARDENT.ink,
|
|
2137
|
+
color: ARDENT.ink,
|
|
2138
|
+
fontFamily: ARDENT.font,
|
|
2139
|
+
fontSize: 16,
|
|
2140
|
+
fontWeight: 600,
|
|
2141
|
+
textTransform: 'none',
|
|
2142
|
+
}}
|
|
2143
|
+
>
|
|
2144
|
+
Review and request quote
|
|
2145
|
+
</Button>
|
|
2146
|
+
</Box>
|
|
2147
|
+
)}
|
|
2148
|
+
{/* Snackbars for a failed save stack upwards from the bottom of the column */}
|
|
2149
|
+
<Box component="div" sx={{ marginBlockStart: 'auto' }}>
|
|
2150
|
+
<LayoutSlots.DesktopSnackbarsRoot />
|
|
2151
|
+
</Box>
|
|
2152
|
+
</Box>
|
|
2153
|
+
</Box>
|
|
2154
|
+
)
|
|
2155
|
+
}
|
|
2156
|
+
```
|
|
2157
|
+
|
|
2158
|
+
### ./src/\_shared/PhonePage.tsx
|
|
2159
|
+
|
|
2160
|
+
```tsx
|
|
2161
|
+
import { Box, Button, ButtonBase, Typography } from '@mui/material'
|
|
2162
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2163
|
+
|
|
2164
|
+
import { LayoutSlots } from '@salsita-ai/sdk/configurator'
|
|
2165
|
+
|
|
2166
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
2167
|
+
import { ARDENT } from '../_form/index.js'
|
|
2168
|
+
import { PriceBreakdown } from './PriceBreakdown.js'
|
|
2169
|
+
import { QuoteAction } from './QuoteAction.js'
|
|
2170
|
+
import { StepForm } from './StepForm.js'
|
|
2171
|
+
import { StepHeader } from './StepHeader.js'
|
|
2172
|
+
|
|
2173
|
+
const CHEVRON_ICON = 'M6 15l6-6 6 6'
|
|
2174
|
+
|
|
2175
|
+
// The phone page: one column with the compact stepper, the active step's form, and a bar at the bottom with the total
|
|
2176
|
+
// and the way on. The total opens the price breakdown as a sheet above the bar.
|
|
2177
|
+
export const PhonePage = () => {
|
|
2178
|
+
const { accessInfo } = phx.frontend.useAccessService()
|
|
2179
|
+
const { activeStep, isLast, steps, next } = phx.frontend.useStepNavigation()
|
|
2180
|
+
const navigationState = phx.frontend.useActiveNavigationState()
|
|
2181
|
+
const compressor = phx.frontend.useRootEntityState()
|
|
2182
|
+
const total = phx.frontend.useRootEntityPrice()
|
|
2183
|
+
const [isBreakdownOpen, setBreakdownOpen] = useState(false)
|
|
2184
|
+
const [revealWarnings, setRevealWarnings] = useState(false)
|
|
2185
|
+
const formRef = useRef<HTMLElement>(null)
|
|
2186
|
+
|
|
2187
|
+
// Each step starts at its top, as a new page would
|
|
2188
|
+
useEffect(() => {
|
|
2189
|
+
formRef.current?.scrollTo({ top: 0 })
|
|
2190
|
+
}, [navigationState])
|
|
2191
|
+
const nextStep = steps[(activeStep?.index ?? -1) + 1]
|
|
2192
|
+
|
|
2193
|
+
return (
|
|
2194
|
+
<Box
|
|
2195
|
+
component="div"
|
|
2196
|
+
sx={{
|
|
2197
|
+
position: 'absolute',
|
|
2198
|
+
inset: 0,
|
|
2199
|
+
display: 'grid',
|
|
2200
|
+
gridTemplateRows: 'auto auto minmax(0, 1fr) auto',
|
|
2201
|
+
bgcolor: ARDENT.page,
|
|
2202
|
+
fontFamily: ARDENT.font,
|
|
2203
|
+
}}
|
|
2204
|
+
>
|
|
2205
|
+
<Box
|
|
2206
|
+
component="header"
|
|
2207
|
+
sx={{
|
|
2208
|
+
display: 'flex',
|
|
2209
|
+
alignItems: 'center',
|
|
2210
|
+
gap: 1.5,
|
|
2211
|
+
paddingInline: 2,
|
|
2212
|
+
blockSize: 52,
|
|
2213
|
+
bgcolor: ARDENT.navy,
|
|
2214
|
+
color: ARDENT.panel,
|
|
2215
|
+
}}
|
|
2216
|
+
>
|
|
2217
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 18, fontWeight: 700, letterSpacing: '0.12em' }}>
|
|
2218
|
+
ARDENT
|
|
2219
|
+
<Box component="span" sx={{ color: ARDENT.signal }}>
|
|
2220
|
+
{' '}
|
|
2221
|
+
AIR
|
|
2222
|
+
</Box>
|
|
2223
|
+
</Typography>
|
|
2224
|
+
<Typography
|
|
2225
|
+
component="h1"
|
|
2226
|
+
sx={{ marginInlineStart: 'auto', fontFamily: ARDENT.font, fontSize: 15, color: '#c9d3e0' }}
|
|
2227
|
+
>
|
|
2228
|
+
Quote · {compressor.$frameName}
|
|
2229
|
+
</Typography>
|
|
2230
|
+
</Box>
|
|
2231
|
+
|
|
2232
|
+
<Box component="div" sx={{ bgcolor: ARDENT.panel, borderBlockEnd: `1px solid ${ARDENT.line}` }}>
|
|
2233
|
+
<StepHeader compact />
|
|
2234
|
+
</Box>
|
|
2235
|
+
|
|
2236
|
+
<Box component="main" ref={formRef} sx={{ overflowY: 'auto', minBlockSize: 0 }}>
|
|
2237
|
+
<Box component="div" sx={{ display: 'grid', gap: 2, padding: 2, paddingBlockEnd: 3 }}>
|
|
2238
|
+
<StepForm readOnly={accessInfo.viewMode} revealWarnings={revealWarnings} />
|
|
2239
|
+
</Box>
|
|
2240
|
+
</Box>
|
|
2241
|
+
|
|
2242
|
+
{isBreakdownOpen && (
|
|
2243
|
+
<Box
|
|
2244
|
+
component="div"
|
|
2245
|
+
onClick={() => setBreakdownOpen(false)}
|
|
2246
|
+
sx={{ position: 'absolute', inset: 0, bgcolor: 'rgba(13, 27, 46, 0.4)' }}
|
|
2247
|
+
/>
|
|
2248
|
+
)}
|
|
2249
|
+
|
|
2250
|
+
<Box
|
|
2251
|
+
component="footer"
|
|
2252
|
+
sx={{
|
|
2253
|
+
position: 'relative',
|
|
2254
|
+
display: 'grid',
|
|
2255
|
+
gap: 1.5,
|
|
2256
|
+
padding: 2,
|
|
2257
|
+
bgcolor: ARDENT.panel,
|
|
2258
|
+
borderBlockStart: `1px solid ${ARDENT.line}`,
|
|
2259
|
+
boxShadow: '0 -8px 24px rgba(13, 27, 46, 0.08)',
|
|
2260
|
+
}}
|
|
2261
|
+
>
|
|
2262
|
+
{isBreakdownOpen && (
|
|
2263
|
+
<Box component="div" sx={{ maxBlockSize: '55vh', overflowY: 'auto', paddingBlockEnd: 1 }}>
|
|
2264
|
+
<PriceBreakdown />
|
|
2265
|
+
</Box>
|
|
2266
|
+
)}
|
|
2267
|
+
<Box component="div" sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
2268
|
+
<ButtonBase
|
|
2269
|
+
aria-expanded={isBreakdownOpen}
|
|
2270
|
+
aria-label={isBreakdownOpen ? 'Hide the price breakdown' : 'Show the price breakdown'}
|
|
2271
|
+
onClick={() => setBreakdownOpen(open => !open)}
|
|
2272
|
+
sx={{ display: 'grid', justifyItems: 'start', textAlign: 'start', borderRadius: 1, paddingInlineEnd: 1 }}
|
|
2273
|
+
>
|
|
2274
|
+
<Typography
|
|
2275
|
+
component="span"
|
|
2276
|
+
sx={{
|
|
2277
|
+
display: 'flex',
|
|
2278
|
+
alignItems: 'center',
|
|
2279
|
+
gap: 0.5,
|
|
2280
|
+
fontFamily: ARDENT.font,
|
|
2281
|
+
fontSize: 13,
|
|
2282
|
+
color: ARDENT.steel,
|
|
2283
|
+
}}
|
|
2284
|
+
>
|
|
2285
|
+
Total, {compressor.$leadTimeWeeks} weeks
|
|
2286
|
+
<Box
|
|
2287
|
+
component="svg"
|
|
2288
|
+
viewBox="0 0 24 24"
|
|
2289
|
+
aria-hidden
|
|
2290
|
+
sx={{
|
|
2291
|
+
inlineSize: 16,
|
|
2292
|
+
blockSize: 16,
|
|
2293
|
+
transform: isBreakdownOpen ? 'rotate(180deg)' : 'none',
|
|
2294
|
+
transition: 'transform 160ms',
|
|
2295
|
+
}}
|
|
2296
|
+
>
|
|
2297
|
+
<path d={CHEVRON_ICON} fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" />
|
|
2298
|
+
</Box>
|
|
2299
|
+
</Typography>
|
|
2300
|
+
<Typography
|
|
2301
|
+
component="span"
|
|
2302
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 22, fontWeight: 700, color: ARDENT.ink }}
|
|
2303
|
+
>
|
|
2304
|
+
{total}
|
|
2305
|
+
</Typography>
|
|
2306
|
+
</ButtonBase>
|
|
2307
|
+
<Box component="div" sx={{ flex: 1, minInlineSize: 0 }}>
|
|
2308
|
+
{isLast ? (
|
|
2309
|
+
navigationState !== 'requested' && <QuoteAction onBlocked={() => setRevealWarnings(true)} compact />
|
|
2310
|
+
) : (
|
|
2311
|
+
<Button
|
|
2312
|
+
fullWidth
|
|
2313
|
+
variant="contained"
|
|
2314
|
+
disableElevation
|
|
2315
|
+
onClick={next}
|
|
2316
|
+
sx={{
|
|
2317
|
+
minBlockSize: 48,
|
|
2318
|
+
borderRadius: 2,
|
|
2319
|
+
bgcolor: ARDENT.navy,
|
|
2320
|
+
fontFamily: ARDENT.font,
|
|
2321
|
+
fontSize: 16,
|
|
2322
|
+
fontWeight: 600,
|
|
2323
|
+
textTransform: 'none',
|
|
2324
|
+
'&:hover': { bgcolor: ARDENT.ink },
|
|
2325
|
+
}}
|
|
2326
|
+
>
|
|
2327
|
+
Next: {nextStep?.label}
|
|
2328
|
+
</Button>
|
|
2329
|
+
)}
|
|
2330
|
+
</Box>
|
|
2331
|
+
</Box>
|
|
2332
|
+
</Box>
|
|
2333
|
+
|
|
2334
|
+
<LayoutSlots.MobileSnackbarsRoot />
|
|
2335
|
+
</Box>
|
|
2336
|
+
)
|
|
2337
|
+
}
|
|
2338
|
+
```
|
|
2339
|
+
|
|
2340
|
+
### ./src/\_shared/PriceBreakdown.tsx
|
|
2341
|
+
|
|
2342
|
+
```tsx
|
|
2343
|
+
import { Box, Typography } from '@mui/material'
|
|
2344
|
+
|
|
2345
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
2346
|
+
import { ARDENT, OVERLINE_SX } from '../_form/index.js'
|
|
2347
|
+
|
|
2348
|
+
const formatEuro = (amount: number) => {
|
|
2349
|
+
return amount.toLocaleString('en-IE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 })
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
const ROW_SX = {
|
|
2353
|
+
display: 'grid',
|
|
2354
|
+
gridTemplateColumns: '1fr auto',
|
|
2355
|
+
columnGap: 2,
|
|
2356
|
+
alignItems: 'baseline',
|
|
2357
|
+
paddingBlock: 1,
|
|
2358
|
+
} as const
|
|
2359
|
+
|
|
2360
|
+
// The quote's lines, subtotal, lead-time surcharge and total. Every number comes from an expression of the spec
|
|
2361
|
+
// (`$quoteLines`, `$subtotal`, `$leadTimeSurcharge`, `$leadTimeWeeks`), read from the root entity, so the breakdown
|
|
2362
|
+
// changes with each choice, and the total is the `price` component's value.
|
|
2363
|
+
export const PriceBreakdown = () => {
|
|
2364
|
+
const compressor = phx.frontend.useRootEntityState()
|
|
2365
|
+
const total = phx.frontend.useRootEntityPrice()
|
|
2366
|
+
|
|
2367
|
+
return (
|
|
2368
|
+
<Box component="section" aria-label="Price breakdown" sx={{ display: 'grid', fontFamily: ARDENT.font }}>
|
|
2369
|
+
<Typography component="h2" sx={{ ...OVERLINE_SX, paddingBlockEnd: 1 }}>
|
|
2370
|
+
Price breakdown
|
|
2371
|
+
</Typography>
|
|
2372
|
+
|
|
2373
|
+
<Box component="ul" sx={{ margin: 0, padding: 0, listStyle: 'none' }}>
|
|
2374
|
+
{compressor.$quoteLines.map(line => (
|
|
2375
|
+
<Box component="li" key={line.key} sx={{ ...ROW_SX, borderBlockEnd: `1px solid ${ARDENT.line}` }}>
|
|
2376
|
+
<Box component="div" sx={{ display: 'grid', minInlineSize: 0 }}>
|
|
2377
|
+
<Typography
|
|
2378
|
+
component="span"
|
|
2379
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, fontWeight: 500, color: ARDENT.ink }}
|
|
2380
|
+
>
|
|
2381
|
+
{line.label}
|
|
2382
|
+
</Typography>
|
|
2383
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 13, color: ARDENT.steel }}>
|
|
2384
|
+
{line.detail}
|
|
2385
|
+
</Typography>
|
|
2386
|
+
</Box>
|
|
2387
|
+
<Typography
|
|
2388
|
+
component="span"
|
|
2389
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.ink, fontVariantNumeric: 'tabular-nums' }}
|
|
2390
|
+
>
|
|
2391
|
+
{formatEuro(line.amount)}
|
|
2392
|
+
</Typography>
|
|
2393
|
+
</Box>
|
|
2394
|
+
))}
|
|
2395
|
+
</Box>
|
|
2396
|
+
|
|
2397
|
+
<Box component="div" sx={{ ...ROW_SX, paddingBlockStart: 1.5 }}>
|
|
2398
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.steel }}>
|
|
2399
|
+
Subtotal
|
|
2400
|
+
</Typography>
|
|
2401
|
+
<Typography
|
|
2402
|
+
component="span"
|
|
2403
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.ink, fontVariantNumeric: 'tabular-nums' }}
|
|
2404
|
+
>
|
|
2405
|
+
{formatEuro(compressor.$subtotal)}
|
|
2406
|
+
</Typography>
|
|
2407
|
+
</Box>
|
|
2408
|
+
{compressor.$leadTimeSurcharge > 0 && (
|
|
2409
|
+
<Box component="div" sx={ROW_SX}>
|
|
2410
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.steel }}>
|
|
2411
|
+
Express delivery surcharge
|
|
2412
|
+
</Typography>
|
|
2413
|
+
<Typography
|
|
2414
|
+
component="span"
|
|
2415
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.ink, fontVariantNumeric: 'tabular-nums' }}
|
|
2416
|
+
>
|
|
2417
|
+
{formatEuro(compressor.$leadTimeSurcharge)}
|
|
2418
|
+
</Typography>
|
|
2419
|
+
</Box>
|
|
2420
|
+
)}
|
|
2421
|
+
|
|
2422
|
+
<Box
|
|
2423
|
+
component="div"
|
|
2424
|
+
sx={{ ...ROW_SX, marginBlockStart: 1, paddingBlockStart: 1.5, borderBlockStart: `2px solid ${ARDENT.ink}` }}
|
|
2425
|
+
>
|
|
2426
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 17, fontWeight: 600, color: ARDENT.ink }}>
|
|
2427
|
+
Total, ex works
|
|
2428
|
+
</Typography>
|
|
2429
|
+
<Typography
|
|
2430
|
+
component="span"
|
|
2431
|
+
sx={{
|
|
2432
|
+
fontFamily: ARDENT.font,
|
|
2433
|
+
fontSize: 26,
|
|
2434
|
+
fontWeight: 700,
|
|
2435
|
+
color: ARDENT.ink,
|
|
2436
|
+
fontVariantNumeric: 'tabular-nums',
|
|
2437
|
+
}}
|
|
2438
|
+
>
|
|
2439
|
+
{total}
|
|
2440
|
+
</Typography>
|
|
2441
|
+
</Box>
|
|
2442
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 14, color: ARDENT.steel, textAlign: 'end' }}>
|
|
2443
|
+
Lead time {compressor.$leadTimeWeeks} weeks · prices excl. VAT
|
|
2444
|
+
</Typography>
|
|
2445
|
+
</Box>
|
|
2446
|
+
)
|
|
2447
|
+
}
|
|
2448
|
+
```
|
|
2449
|
+
|
|
2450
|
+
### ./src/\_shared/QuoteAction.tsx
|
|
2451
|
+
|
|
2452
|
+
```tsx
|
|
2453
|
+
import { Alert, Box, Button, CircularProgress, Typography } from '@mui/material'
|
|
2454
|
+
|
|
2455
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
2456
|
+
import { ARDENT } from '../_form/index.js'
|
|
2457
|
+
|
|
2458
|
+
interface QuoteActionProps {
|
|
2459
|
+
// Called instead of running the action while a warning blocks it, so the layout can show every warning
|
|
2460
|
+
readonly onBlocked: () => void
|
|
2461
|
+
// Leaves out the list of what blocks the request, for the phone's bottom bar
|
|
2462
|
+
readonly compact: boolean
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
// The spec's `quote` finalize action. While any warning holds, `useFinalizeActions` reports the action `disabled` with
|
|
2466
|
+
// the warnings as `hints`: the button stays clickable and reveals them instead of running. A viewer sees no button.
|
|
2467
|
+
export const QuoteAction = ({ onBlocked, compact }: QuoteActionProps) => {
|
|
2468
|
+
const { primary, hints } = phx.frontend.useFinalizeActions()
|
|
2469
|
+
const { accessInfo } = phx.frontend.useAccessService()
|
|
2470
|
+
|
|
2471
|
+
if (primary === null || accessInfo.viewMode) {
|
|
2472
|
+
return null
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
return (
|
|
2476
|
+
<Box component="div" sx={{ display: 'grid', gap: 1.25 }}>
|
|
2477
|
+
<Button
|
|
2478
|
+
variant="contained"
|
|
2479
|
+
disableElevation
|
|
2480
|
+
disabled={primary.isPending}
|
|
2481
|
+
aria-disabled={primary.disabled}
|
|
2482
|
+
title={primary.title}
|
|
2483
|
+
onClick={() => (primary.disabled ? onBlocked() : void primary.run())}
|
|
2484
|
+
startIcon={primary.isPending ? <CircularProgress size={18} color="inherit" /> : undefined}
|
|
2485
|
+
sx={{
|
|
2486
|
+
minBlockSize: 52,
|
|
2487
|
+
borderRadius: 2,
|
|
2488
|
+
bgcolor: primary.disabled ? ARDENT.steel : ARDENT.signal,
|
|
2489
|
+
color: ARDENT.panel,
|
|
2490
|
+
fontFamily: ARDENT.font,
|
|
2491
|
+
fontSize: 17,
|
|
2492
|
+
fontWeight: 600,
|
|
2493
|
+
textTransform: 'none',
|
|
2494
|
+
'&:hover': { bgcolor: primary.disabled ? ARDENT.steel : ARDENT.signalDark },
|
|
2495
|
+
'&.Mui-disabled': { bgcolor: ARDENT.signal, color: ARDENT.panel, opacity: 0.7 },
|
|
2496
|
+
}}
|
|
2497
|
+
>
|
|
2498
|
+
{primary.isPending ? 'Sending to an engineer…' : primary.label}
|
|
2499
|
+
</Button>
|
|
2500
|
+
{!compact && primary.disabled && hints && (
|
|
2501
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 14, lineHeight: 1.4, color: ARDENT.warning }}>
|
|
2502
|
+
Resolve before requesting: {hints}
|
|
2503
|
+
</Typography>
|
|
2504
|
+
)}
|
|
2505
|
+
{primary.error && (
|
|
2506
|
+
<Alert severity="error">The quote could not be sent: {primary.error.message} Try again in a moment.</Alert>
|
|
2507
|
+
)}
|
|
2508
|
+
</Box>
|
|
2509
|
+
)
|
|
2510
|
+
}
|
|
2511
|
+
```
|
|
2512
|
+
|
|
2513
|
+
### ./src/\_shared/ReviewStep.tsx
|
|
2514
|
+
|
|
2515
|
+
```tsx
|
|
2516
|
+
import { Box, Button, Typography } from '@mui/material'
|
|
2517
|
+
|
|
2518
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
2519
|
+
import { ARDENT, OVERLINE_SX, TextInputField, useWarnings } from '../_form/index.js'
|
|
2520
|
+
import { setQuoteReceipt, useQuoteReceipt } from './quote-receipt.js'
|
|
2521
|
+
import { STEP_WARNINGS } from './step-warnings.js'
|
|
2522
|
+
|
|
2523
|
+
const CARD_SX = {
|
|
2524
|
+
display: 'grid',
|
|
2525
|
+
gap: 2.5,
|
|
2526
|
+
padding: { xs: 2.5, sm: 3 },
|
|
2527
|
+
borderRadius: 3,
|
|
2528
|
+
border: `1px solid ${ARDENT.line}`,
|
|
2529
|
+
bgcolor: ARDENT.panel,
|
|
2530
|
+
} as const
|
|
2531
|
+
|
|
2532
|
+
// The quote service's answer, kept visible after the request: what to quote against and who answers
|
|
2533
|
+
const Receipt = () => {
|
|
2534
|
+
const receipt = useQuoteReceipt()
|
|
2535
|
+
const navigate = phx.frontend.useNavigate()
|
|
2536
|
+
// The answer lives only as long as the page; a configuration reopened in the `requested` state shows no details
|
|
2537
|
+
const rows =
|
|
2538
|
+
receipt === null
|
|
2539
|
+
? []
|
|
2540
|
+
: [
|
|
2541
|
+
{ label: 'Reference', value: receipt.reference },
|
|
2542
|
+
{ label: 'Your engineer', value: receipt.engineer },
|
|
2543
|
+
{ label: 'Price valid until', value: receipt.validUntil },
|
|
2544
|
+
{ label: 'Quoted total', value: receipt.total },
|
|
2545
|
+
]
|
|
2546
|
+
|
|
2547
|
+
return (
|
|
2548
|
+
<Box
|
|
2549
|
+
component="section"
|
|
2550
|
+
role="status"
|
|
2551
|
+
sx={{ ...CARD_SX, borderColor: ARDENT.success, bgcolor: ARDENT.successTint }}
|
|
2552
|
+
>
|
|
2553
|
+
<Box component="header" sx={{ display: 'grid', gap: 0.5 }}>
|
|
2554
|
+
<Typography
|
|
2555
|
+
component="h3"
|
|
2556
|
+
sx={{ fontFamily: ARDENT.font, fontSize: 24, fontWeight: 700, color: ARDENT.success }}
|
|
2557
|
+
>
|
|
2558
|
+
Quote requested
|
|
2559
|
+
</Typography>
|
|
2560
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.ink }}>
|
|
2561
|
+
Your engineer checks the duty and sends a firm quote within one working day.
|
|
2562
|
+
</Typography>
|
|
2563
|
+
</Box>
|
|
2564
|
+
<Box
|
|
2565
|
+
component="dl"
|
|
2566
|
+
sx={{ display: 'grid', gridTemplateColumns: 'max-content 1fr', columnGap: 3, rowGap: 1, margin: 0 }}
|
|
2567
|
+
>
|
|
2568
|
+
{rows.map(row => (
|
|
2569
|
+
<Box component="div" key={row.label} sx={{ display: 'contents' }}>
|
|
2570
|
+
<Typography component="dt" sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.steel }}>
|
|
2571
|
+
{row.label}
|
|
2572
|
+
</Typography>
|
|
2573
|
+
<Typography
|
|
2574
|
+
component="dd"
|
|
2575
|
+
sx={{ margin: 0, fontFamily: ARDENT.font, fontSize: 15, fontWeight: 600, color: ARDENT.ink }}
|
|
2576
|
+
>
|
|
2577
|
+
{row.value}
|
|
2578
|
+
</Typography>
|
|
2579
|
+
</Box>
|
|
2580
|
+
))}
|
|
2581
|
+
</Box>
|
|
2582
|
+
<Box component="div">
|
|
2583
|
+
<Button
|
|
2584
|
+
variant="outlined"
|
|
2585
|
+
onClick={() => {
|
|
2586
|
+
setQuoteReceipt(null)
|
|
2587
|
+
navigate('review')
|
|
2588
|
+
}}
|
|
2589
|
+
sx={{
|
|
2590
|
+
borderColor: ARDENT.ink,
|
|
2591
|
+
color: ARDENT.ink,
|
|
2592
|
+
fontFamily: ARDENT.font,
|
|
2593
|
+
fontSize: 15,
|
|
2594
|
+
textTransform: 'none',
|
|
2595
|
+
}}
|
|
2596
|
+
>
|
|
2597
|
+
Back to summary
|
|
2598
|
+
</Button>
|
|
2599
|
+
</Box>
|
|
2600
|
+
</Box>
|
|
2601
|
+
)
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
// The conflicts left between choices, each with a way to the step that resolves it
|
|
2605
|
+
const Conflicts = () => {
|
|
2606
|
+
const warnings = useWarnings()
|
|
2607
|
+
const { steps, goTo } = phx.frontend.useStepNavigation()
|
|
2608
|
+
const conflicts = warnings.flatMap(warning => {
|
|
2609
|
+
const step = steps.find(candidate => STEP_WARNINGS[candidate.state]?.includes(warning.type))
|
|
2610
|
+
|
|
2611
|
+
return step ? [{ ...warning, step }] : []
|
|
2612
|
+
})
|
|
2613
|
+
|
|
2614
|
+
if (conflicts.length === 0) {
|
|
2615
|
+
return null
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
return (
|
|
2619
|
+
<Box
|
|
2620
|
+
component="section"
|
|
2621
|
+
role="alert"
|
|
2622
|
+
sx={{ ...CARD_SX, gap: 1.5, borderColor: ARDENT.warning, bgcolor: ARDENT.warningTint }}
|
|
2623
|
+
>
|
|
2624
|
+
<Typography component="h3" sx={{ fontFamily: ARDENT.font, fontSize: 18, fontWeight: 600, color: ARDENT.warning }}>
|
|
2625
|
+
{conflicts.length === 1 ? 'One choice needs attention' : `${conflicts.length} choices need attention`}
|
|
2626
|
+
</Typography>
|
|
2627
|
+
{conflicts.map(conflict => (
|
|
2628
|
+
<Box
|
|
2629
|
+
component="div"
|
|
2630
|
+
key={conflict.type}
|
|
2631
|
+
sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 2 }}
|
|
2632
|
+
>
|
|
2633
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.ink }}>{conflict.text}</Typography>
|
|
2634
|
+
<Button
|
|
2635
|
+
onClick={() => goTo(conflict.step.index)}
|
|
2636
|
+
sx={{
|
|
2637
|
+
flex: 'none',
|
|
2638
|
+
color: ARDENT.warning,
|
|
2639
|
+
fontFamily: ARDENT.font,
|
|
2640
|
+
fontSize: 15,
|
|
2641
|
+
fontWeight: 600,
|
|
2642
|
+
textTransform: 'none',
|
|
2643
|
+
}}
|
|
2644
|
+
>
|
|
2645
|
+
Go to {conflict.step.label}
|
|
2646
|
+
</Button>
|
|
2647
|
+
</Box>
|
|
2648
|
+
))}
|
|
2649
|
+
</Box>
|
|
2650
|
+
)
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
interface ReviewStepProps {
|
|
2654
|
+
readonly readOnly: boolean
|
|
2655
|
+
readonly revealWarnings: boolean
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
// The last step: the conflicts left, the contact details the quote needs, and the summary of what is quoted, from the
|
|
2659
|
+
// spec's `$specSheet` expression. After the request, the service's answer takes the place of the contact details.
|
|
2660
|
+
export const ReviewStep = ({ readOnly, revealWarnings }: ReviewStepProps) => {
|
|
2661
|
+
const compressor = phx.frontend.useRootEntityState()
|
|
2662
|
+
const navigationState = phx.frontend.useActiveNavigationState()
|
|
2663
|
+
const isRequested = navigationState === 'requested'
|
|
2664
|
+
|
|
2665
|
+
return (
|
|
2666
|
+
<>
|
|
2667
|
+
{isRequested ? (
|
|
2668
|
+
<Receipt />
|
|
2669
|
+
) : (
|
|
2670
|
+
<>
|
|
2671
|
+
<Conflicts />
|
|
2672
|
+
<Box component="section" sx={CARD_SX}>
|
|
2673
|
+
<Typography component="h3" sx={OVERLINE_SX}>
|
|
2674
|
+
Your details
|
|
2675
|
+
</Typography>
|
|
2676
|
+
<Box component="div" sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2.5 }}>
|
|
2677
|
+
<TextInputField
|
|
2678
|
+
instanceName="company"
|
|
2679
|
+
label="Company"
|
|
2680
|
+
warningTypes={['companyMissing']}
|
|
2681
|
+
readOnly={readOnly}
|
|
2682
|
+
autoComplete="organization"
|
|
2683
|
+
revealWarning={revealWarnings}
|
|
2684
|
+
/>
|
|
2685
|
+
<TextInputField
|
|
2686
|
+
instanceName="email"
|
|
2687
|
+
label="Email"
|
|
2688
|
+
type="email"
|
|
2689
|
+
warningTypes={['emailInvalid']}
|
|
2690
|
+
readOnly={readOnly}
|
|
2691
|
+
autoComplete="email"
|
|
2692
|
+
revealWarning={revealWarnings}
|
|
2693
|
+
/>
|
|
2694
|
+
</Box>
|
|
2695
|
+
<TextInputField
|
|
2696
|
+
instanceName="projectReference"
|
|
2697
|
+
label="Project reference"
|
|
2698
|
+
hint="Optional. It is printed on the quote."
|
|
2699
|
+
readOnly={readOnly}
|
|
2700
|
+
revealWarning={revealWarnings}
|
|
2701
|
+
/>
|
|
2702
|
+
</Box>
|
|
2703
|
+
</>
|
|
2704
|
+
)}
|
|
2705
|
+
|
|
2706
|
+
<Box component="section" sx={CARD_SX}>
|
|
2707
|
+
<Typography component="h3" sx={OVERLINE_SX}>
|
|
2708
|
+
{isRequested ? 'What was quoted' : 'Quote summary'}
|
|
2709
|
+
</Typography>
|
|
2710
|
+
<Box
|
|
2711
|
+
component="dl"
|
|
2712
|
+
sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' }, columnGap: 4, margin: 0 }}
|
|
2713
|
+
>
|
|
2714
|
+
{compressor.$specSheet.map(row => (
|
|
2715
|
+
<Box
|
|
2716
|
+
component="div"
|
|
2717
|
+
key={row.label}
|
|
2718
|
+
sx={{
|
|
2719
|
+
display: 'flex',
|
|
2720
|
+
justifyContent: 'space-between',
|
|
2721
|
+
gap: 2,
|
|
2722
|
+
paddingBlock: 1,
|
|
2723
|
+
borderBlockEnd: `1px solid ${ARDENT.line}`,
|
|
2724
|
+
}}
|
|
2725
|
+
>
|
|
2726
|
+
<Typography component="dt" sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.steel }}>
|
|
2727
|
+
{row.label}
|
|
2728
|
+
</Typography>
|
|
2729
|
+
<Typography
|
|
2730
|
+
component="dd"
|
|
2731
|
+
sx={{
|
|
2732
|
+
margin: 0,
|
|
2733
|
+
fontFamily: ARDENT.font,
|
|
2734
|
+
fontSize: 15,
|
|
2735
|
+
fontWeight: 500,
|
|
2736
|
+
color: ARDENT.ink,
|
|
2737
|
+
textAlign: 'end',
|
|
2738
|
+
}}
|
|
2739
|
+
>
|
|
2740
|
+
{row.value}
|
|
2741
|
+
</Typography>
|
|
2742
|
+
</Box>
|
|
2743
|
+
))}
|
|
2744
|
+
</Box>
|
|
2745
|
+
</Box>
|
|
2746
|
+
</>
|
|
2747
|
+
)
|
|
2748
|
+
}
|
|
2749
|
+
```
|
|
2750
|
+
|
|
2751
|
+
### ./src/\_shared/StepForm.tsx
|
|
2752
|
+
|
|
2753
|
+
```tsx
|
|
2754
|
+
import { Box, Typography } from '@mui/material'
|
|
2755
|
+
import type { ReactNode } from 'react'
|
|
2756
|
+
|
|
2757
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
2758
|
+
import { ARDENT, ChoiceField, NumberField, OVERLINE_SX, SwitchField } from '../_form/index.js'
|
|
2759
|
+
import { ReviewStep } from './ReviewStep.js'
|
|
2760
|
+
|
|
2761
|
+
interface SectionProps {
|
|
2762
|
+
readonly title: string
|
|
2763
|
+
readonly description?: string
|
|
2764
|
+
readonly children: ReactNode
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
const Section = ({ title, description, children }: SectionProps) => {
|
|
2768
|
+
return (
|
|
2769
|
+
<Box
|
|
2770
|
+
component="section"
|
|
2771
|
+
sx={{
|
|
2772
|
+
display: 'grid',
|
|
2773
|
+
gap: 3,
|
|
2774
|
+
padding: { xs: 2.5, sm: 3 },
|
|
2775
|
+
borderRadius: 3,
|
|
2776
|
+
border: `1px solid ${ARDENT.line}`,
|
|
2777
|
+
bgcolor: ARDENT.panel,
|
|
2778
|
+
}}
|
|
2779
|
+
>
|
|
2780
|
+
<Box component="header" sx={{ display: 'grid', gap: 0.5 }}>
|
|
2781
|
+
<Typography component="h3" sx={OVERLINE_SX}>
|
|
2782
|
+
{title}
|
|
2783
|
+
</Typography>
|
|
2784
|
+
{description && (
|
|
2785
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 15, color: ARDENT.steel }}>{description}</Typography>
|
|
2786
|
+
)}
|
|
2787
|
+
</Box>
|
|
2788
|
+
{children}
|
|
2789
|
+
</Box>
|
|
2790
|
+
)
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// The frame the duty selects, from the spec's expressions, so the customer sees what the numbers mean
|
|
2794
|
+
const FrameStrip = () => {
|
|
2795
|
+
const compressor = phx.frontend.useRootEntityState()
|
|
2796
|
+
|
|
2797
|
+
return (
|
|
2798
|
+
<Box
|
|
2799
|
+
component="div"
|
|
2800
|
+
role="status"
|
|
2801
|
+
sx={{
|
|
2802
|
+
display: 'flex',
|
|
2803
|
+
flexWrap: 'wrap',
|
|
2804
|
+
alignItems: 'baseline',
|
|
2805
|
+
columnGap: 1.5,
|
|
2806
|
+
rowGap: 0.5,
|
|
2807
|
+
paddingInline: 2,
|
|
2808
|
+
paddingBlock: 1.5,
|
|
2809
|
+
borderRadius: 2,
|
|
2810
|
+
bgcolor: ARDENT.navy,
|
|
2811
|
+
color: ARDENT.panel,
|
|
2812
|
+
fontFamily: ARDENT.font,
|
|
2813
|
+
}}
|
|
2814
|
+
>
|
|
2815
|
+
<Typography component="span" sx={{ ...OVERLINE_SX, color: '#9fb0c6' }}>
|
|
2816
|
+
Selected frame
|
|
2817
|
+
</Typography>
|
|
2818
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 20, fontWeight: 700 }}>
|
|
2819
|
+
{compressor.$frameName}
|
|
2820
|
+
</Typography>
|
|
2821
|
+
<Typography component="span" sx={{ fontFamily: ARDENT.font, fontSize: 15, color: '#c9d3e0' }}>
|
|
2822
|
+
{compressor.$frameKw} kW motor, the smallest frame that meets the duty
|
|
2823
|
+
</Typography>
|
|
2824
|
+
</Box>
|
|
2825
|
+
)
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
interface StepFormProps {
|
|
2829
|
+
readonly readOnly: boolean
|
|
2830
|
+
readonly revealWarnings: boolean
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
// The form of the active step. Each section lists the fields the step asks for; a field whose spec component is
|
|
2834
|
+
// inactive, such as the dew point without a desiccant dryer, renders nothing, so the conditions stay in the spec.
|
|
2835
|
+
export const StepForm = ({ readOnly, revealWarnings }: StepFormProps) => {
|
|
2836
|
+
const navigationState = phx.frontend.useActiveNavigationState()
|
|
2837
|
+
const compressor = phx.frontend.useRootEntityState()
|
|
2838
|
+
const receiverHint =
|
|
2839
|
+
compressor.$minReceiverLitres > 0
|
|
2840
|
+
? `A fixed-speed machine at this flow needs at least ${compressor.$minReceiverLitres.toLocaleString('en')} L.`
|
|
2841
|
+
: 'A variable-speed machine follows demand and needs no large buffer.'
|
|
2842
|
+
|
|
2843
|
+
switch (navigationState) {
|
|
2844
|
+
case 'duty':
|
|
2845
|
+
return (
|
|
2846
|
+
<>
|
|
2847
|
+
<Section title="Air demand" description="How much air the plant draws at peak, and at what pressure.">
|
|
2848
|
+
<NumberField
|
|
2849
|
+
instanceName="flow"
|
|
2850
|
+
label="Free air delivery"
|
|
2851
|
+
hint="Read it from the plate of the machine you replace, or from a flow log."
|
|
2852
|
+
warningTypes={['flowAboveRange']}
|
|
2853
|
+
readOnly={readOnly}
|
|
2854
|
+
step={0.5}
|
|
2855
|
+
/>
|
|
2856
|
+
<ChoiceField instanceName="pressure" label="Working pressure" readOnly={readOnly} />
|
|
2857
|
+
<FrameStrip />
|
|
2858
|
+
</Section>
|
|
2859
|
+
<Section title="Drive and duty">
|
|
2860
|
+
<ChoiceField instanceName="drive" label="Drive" readOnly={readOnly} minCardWidth={200} />
|
|
2861
|
+
<ChoiceField instanceName="dutyCycle" label="Duty cycle" readOnly={readOnly} minCardWidth={200} />
|
|
2862
|
+
</Section>
|
|
2863
|
+
</>
|
|
2864
|
+
)
|
|
2865
|
+
case 'treatment':
|
|
2866
|
+
return (
|
|
2867
|
+
<>
|
|
2868
|
+
<Section title="Drying" description="Compressed air leaves the machine saturated with water.">
|
|
2869
|
+
<ChoiceField instanceName="dryer" label="Dryer" readOnly={readOnly} />
|
|
2870
|
+
<ChoiceField instanceName="dewPoint" label="Pressure dew point" readOnly={readOnly} minCardWidth={200} />
|
|
2871
|
+
</Section>
|
|
2872
|
+
<Section title="Filtration">
|
|
2873
|
+
<ChoiceField
|
|
2874
|
+
instanceName="filtration"
|
|
2875
|
+
label="Filter package"
|
|
2876
|
+
warningTypes={['desiccantNeedsFiltration']}
|
|
2877
|
+
readOnly={readOnly}
|
|
2878
|
+
/>
|
|
2879
|
+
</Section>
|
|
2880
|
+
</>
|
|
2881
|
+
)
|
|
2882
|
+
case 'installation':
|
|
2883
|
+
return (
|
|
2884
|
+
<>
|
|
2885
|
+
<Section title="Plant room">
|
|
2886
|
+
<ChoiceField instanceName="cooling" label="Cooling" readOnly={readOnly} minCardWidth={200} />
|
|
2887
|
+
<SwitchField
|
|
2888
|
+
instanceName="heatRecovery"
|
|
2889
|
+
label="Heat recovery"
|
|
2890
|
+
description="Turns up to 70 % of the motor's power into hot water for the plant."
|
|
2891
|
+
readOnly={readOnly}
|
|
2892
|
+
/>
|
|
2893
|
+
<ChoiceField instanceName="enclosure" label="Canopy" readOnly={readOnly} minCardWidth={200} />
|
|
2894
|
+
</Section>
|
|
2895
|
+
<Section title="Air network">
|
|
2896
|
+
<ChoiceField
|
|
2897
|
+
instanceName="receiver"
|
|
2898
|
+
label="Air receiver"
|
|
2899
|
+
hint={receiverHint}
|
|
2900
|
+
warningTypes={['receiverTooSmall']}
|
|
2901
|
+
readOnly={readOnly}
|
|
2902
|
+
minCardWidth={110}
|
|
2903
|
+
/>
|
|
2904
|
+
<ChoiceField instanceName="supply" label="Electrical supply" readOnly={readOnly} minCardWidth={200} />
|
|
2905
|
+
</Section>
|
|
2906
|
+
</>
|
|
2907
|
+
)
|
|
2908
|
+
case 'service':
|
|
2909
|
+
return (
|
|
2910
|
+
<>
|
|
2911
|
+
<Section title="Service">
|
|
2912
|
+
<ChoiceField instanceName="servicePlan" label="Service plan" readOnly={readOnly} />
|
|
2913
|
+
<SwitchField
|
|
2914
|
+
instanceName="commissioning"
|
|
2915
|
+
label="Commissioning on site"
|
|
2916
|
+
description="An Ardent engineer starts the machine and trains your staff."
|
|
2917
|
+
readOnly={readOnly}
|
|
2918
|
+
/>
|
|
2919
|
+
</Section>
|
|
2920
|
+
<Section title="Delivery" description="Lead times are ex works, from the order's confirmation.">
|
|
2921
|
+
<ChoiceField
|
|
2922
|
+
instanceName="delivery"
|
|
2923
|
+
label="Delivery"
|
|
2924
|
+
warningTypes={['expressUnavailable']}
|
|
2925
|
+
readOnly={readOnly}
|
|
2926
|
+
minCardWidth={200}
|
|
2927
|
+
/>
|
|
2928
|
+
</Section>
|
|
2929
|
+
</>
|
|
2930
|
+
)
|
|
2931
|
+
default:
|
|
2932
|
+
return <ReviewStep readOnly={readOnly} revealWarnings={revealWarnings} />
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
```
|
|
2936
|
+
|
|
2937
|
+
### ./src/\_shared/StepHeader.tsx
|
|
2938
|
+
|
|
2939
|
+
```tsx
|
|
2940
|
+
import { Box, ButtonBase, Typography } from '@mui/material'
|
|
2941
|
+
|
|
2942
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
2943
|
+
import { ARDENT, useWarnings } from '../_form/index.js'
|
|
2944
|
+
import { STEP_WARNINGS } from './step-warnings.js'
|
|
2945
|
+
|
|
2946
|
+
const CHECK_ICON = 'M5 12.5 10 17.5 19 7'
|
|
2947
|
+
|
|
2948
|
+
interface StepHeaderProps {
|
|
2949
|
+
readonly compact: boolean
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// The spec's `navigationSteps` as a stepper. Every step can be entered, in any order; a step that holds a warning
|
|
2953
|
+
// carries a mark until the warning is resolved. On a phone the stepper is one line with a segmented progress bar.
|
|
2954
|
+
export const StepHeader = ({ compact }: StepHeaderProps) => {
|
|
2955
|
+
const { steps, activeStep, goTo } = phx.frontend.useStepNavigation()
|
|
2956
|
+
const warnings = useWarnings()
|
|
2957
|
+
|
|
2958
|
+
const hasWarning = (states: readonly string[]) => {
|
|
2959
|
+
return states.some(state => warnings.some(warning => STEP_WARNINGS[state]?.includes(warning.type)))
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
if (compact) {
|
|
2963
|
+
return (
|
|
2964
|
+
<Box
|
|
2965
|
+
component="nav"
|
|
2966
|
+
aria-label="Quote steps"
|
|
2967
|
+
sx={{ display: 'grid', gap: 1, paddingInline: 2, paddingBlock: 1.5 }}
|
|
2968
|
+
>
|
|
2969
|
+
<Box component="div" sx={{ display: 'flex', alignItems: 'baseline', gap: 1 }}>
|
|
2970
|
+
<Typography sx={{ fontFamily: ARDENT.font, fontSize: 13, color: ARDENT.steel }}>
|
|
2971
|
+
Step {(activeStep?.index ?? 0) + 1} of {steps.length}
|
|
2972
|
+
</Typography>
|
|
2973
|
+
<Typography component="h2" sx={{ fontFamily: ARDENT.font, fontSize: 17, fontWeight: 600, color: ARDENT.ink }}>
|
|
2974
|
+
{activeStep?.label}
|
|
2975
|
+
</Typography>
|
|
2976
|
+
</Box>
|
|
2977
|
+
<Box component="ol" sx={{ display: 'flex', gap: 0.5, margin: 0, padding: 0, listStyle: 'none' }}>
|
|
2978
|
+
{steps.map(step => (
|
|
2979
|
+
<Box component="li" key={step.state} sx={{ flex: 1 }}>
|
|
2980
|
+
<ButtonBase
|
|
2981
|
+
aria-label={`${step.label}${hasWarning(step.navigationStates) ? ', needs attention' : ''}`}
|
|
2982
|
+
aria-current={step.status === 'active' ? 'step' : undefined}
|
|
2983
|
+
onClick={() => goTo(step.index)}
|
|
2984
|
+
sx={{
|
|
2985
|
+
display: 'block',
|
|
2986
|
+
inlineSize: '100%',
|
|
2987
|
+
blockSize: 6,
|
|
2988
|
+
borderRadius: 3,
|
|
2989
|
+
// A bigger target than the bar itself, for a finger
|
|
2990
|
+
outline: '8px solid transparent',
|
|
2991
|
+
bgcolor: hasWarning(step.navigationStates)
|
|
2992
|
+
? ARDENT.warning
|
|
2993
|
+
: step.status === 'upcoming'
|
|
2994
|
+
? ARDENT.line
|
|
2995
|
+
: ARDENT.signal,
|
|
2996
|
+
}}
|
|
2997
|
+
/>
|
|
2998
|
+
</Box>
|
|
2999
|
+
))}
|
|
3000
|
+
</Box>
|
|
3001
|
+
</Box>
|
|
3002
|
+
)
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
return (
|
|
3006
|
+
<Box
|
|
3007
|
+
component="nav"
|
|
3008
|
+
aria-label="Quote steps"
|
|
3009
|
+
sx={{ display: 'flex', alignItems: 'center', gap: 1, paddingInline: 4, paddingBlock: 1.5, overflowX: 'auto' }}
|
|
3010
|
+
>
|
|
3011
|
+
{steps.map((step, index) => {
|
|
3012
|
+
const warn = hasWarning(step.navigationStates)
|
|
3013
|
+
const isActive = step.status === 'active'
|
|
3014
|
+
const isComplete = step.status === 'complete'
|
|
3015
|
+
|
|
3016
|
+
return (
|
|
3017
|
+
<Box component="div" key={step.state} sx={{ display: 'flex', alignItems: 'center', gap: 1, flex: 'none' }}>
|
|
3018
|
+
{index > 0 && (
|
|
3019
|
+
<Box
|
|
3020
|
+
component="span"
|
|
3021
|
+
aria-hidden
|
|
3022
|
+
sx={{ inlineSize: 32, blockSize: 2, bgcolor: isComplete || isActive ? ARDENT.signal : ARDENT.line }}
|
|
3023
|
+
/>
|
|
3024
|
+
)}
|
|
3025
|
+
<ButtonBase
|
|
3026
|
+
aria-current={isActive ? 'step' : undefined}
|
|
3027
|
+
onClick={() => goTo(step.index)}
|
|
3028
|
+
sx={{
|
|
3029
|
+
display: 'flex',
|
|
3030
|
+
alignItems: 'center',
|
|
3031
|
+
gap: 1,
|
|
3032
|
+
paddingInline: 1,
|
|
3033
|
+
paddingBlock: 0.75,
|
|
3034
|
+
borderRadius: 2,
|
|
3035
|
+
'&:hover': { bgcolor: ARDENT.page },
|
|
3036
|
+
'&.Mui-focusVisible': { outline: `2px solid ${ARDENT.navy}` },
|
|
3037
|
+
}}
|
|
3038
|
+
>
|
|
3039
|
+
<Box
|
|
3040
|
+
component="span"
|
|
3041
|
+
sx={{
|
|
3042
|
+
display: 'grid',
|
|
3043
|
+
placeItems: 'center',
|
|
3044
|
+
inlineSize: 28,
|
|
3045
|
+
blockSize: 28,
|
|
3046
|
+
borderRadius: '50%',
|
|
3047
|
+
fontFamily: ARDENT.font,
|
|
3048
|
+
fontSize: 14,
|
|
3049
|
+
fontWeight: 600,
|
|
3050
|
+
color: isActive || isComplete ? ARDENT.panel : ARDENT.steel,
|
|
3051
|
+
bgcolor: isActive ? ARDENT.signal : isComplete ? ARDENT.navy : 'transparent',
|
|
3052
|
+
border: isActive || isComplete ? 'none' : `2px solid ${ARDENT.line}`,
|
|
3053
|
+
}}
|
|
3054
|
+
>
|
|
3055
|
+
{isComplete ? (
|
|
3056
|
+
<Box component="svg" viewBox="0 0 24 24" aria-hidden sx={{ inlineSize: 16, blockSize: 16 }}>
|
|
3057
|
+
<path d={CHECK_ICON} fill="none" stroke="currentColor" strokeWidth={3} strokeLinecap="round" />
|
|
3058
|
+
</Box>
|
|
3059
|
+
) : (
|
|
3060
|
+
index + 1
|
|
3061
|
+
)}
|
|
3062
|
+
</Box>
|
|
3063
|
+
<Typography
|
|
3064
|
+
component="span"
|
|
3065
|
+
sx={{
|
|
3066
|
+
fontFamily: ARDENT.font,
|
|
3067
|
+
fontSize: 15,
|
|
3068
|
+
fontWeight: isActive ? 600 : 500,
|
|
3069
|
+
color: isActive ? ARDENT.ink : ARDENT.steel,
|
|
3070
|
+
whiteSpace: 'nowrap',
|
|
3071
|
+
}}
|
|
3072
|
+
>
|
|
3073
|
+
{step.label}
|
|
3074
|
+
</Typography>
|
|
3075
|
+
{warn && (
|
|
3076
|
+
<Box
|
|
3077
|
+
component="span"
|
|
3078
|
+
role="img"
|
|
3079
|
+
aria-label="needs attention"
|
|
3080
|
+
sx={{ inlineSize: 8, blockSize: 8, borderRadius: '50%', bgcolor: ARDENT.warning }}
|
|
3081
|
+
/>
|
|
3082
|
+
)}
|
|
3083
|
+
</ButtonBase>
|
|
3084
|
+
</Box>
|
|
3085
|
+
)
|
|
3086
|
+
})}
|
|
3087
|
+
</Box>
|
|
3088
|
+
)
|
|
3089
|
+
}
|
|
3090
|
+
```
|
|
3091
|
+
|
|
3092
|
+
### ./src/\_shared/quote-receipt.ts
|
|
3093
|
+
|
|
3094
|
+
```ts
|
|
3095
|
+
import { useSyncExternalStore } from 'react'
|
|
3096
|
+
|
|
3097
|
+
/** What the quote service answers when a quote is requested: the reference the engineer will quote against */
|
|
3098
|
+
export interface QuoteReceipt {
|
|
3099
|
+
readonly reference: string
|
|
3100
|
+
readonly engineer: string
|
|
3101
|
+
readonly validUntil: string
|
|
3102
|
+
readonly total: string
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
let receipt: QuoteReceipt | null = null
|
|
3106
|
+
const listeners = new Set<() => void>()
|
|
3107
|
+
|
|
3108
|
+
const subscribe = (listener: () => void) => {
|
|
3109
|
+
listeners.add(listener)
|
|
3110
|
+
|
|
3111
|
+
return () => {
|
|
3112
|
+
listeners.delete(listener)
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// A finalize action's `run()` resolves without a value, so the `requestQuote` handler keeps the service's answer here
|
|
3117
|
+
// for the layout to show. The project owns this store; the configurator does not persist it.
|
|
3118
|
+
export const setQuoteReceipt = (next: QuoteReceipt | null) => {
|
|
3119
|
+
receipt = next
|
|
3120
|
+
listeners.forEach(listener => listener())
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
export const useQuoteReceipt = () => {
|
|
3124
|
+
return useSyncExternalStore(subscribe, () => receipt)
|
|
3125
|
+
}
|
|
3126
|
+
```
|
|
3127
|
+
|
|
3128
|
+
### ./src/\_shared/step-warnings.ts
|
|
3129
|
+
|
|
3130
|
+
```ts
|
|
3131
|
+
// Which step resolves each conflict between choices: the stepper marks a step that holds one, and the review sends the
|
|
3132
|
+
// customer to the step that can fix it. The contact details' warnings stay with their fields on the review step.
|
|
3133
|
+
export const STEP_WARNINGS: Readonly<Record<string, readonly string[]>> = {
|
|
3134
|
+
duty: ['flowAboveRange'],
|
|
3135
|
+
treatment: ['desiccantNeedsFiltration'],
|
|
3136
|
+
installation: ['receiverTooSmall'],
|
|
3137
|
+
service: ['expressUnavailable'],
|
|
3138
|
+
}
|
|
3139
|
+
```
|
|
3140
|
+
|
|
3141
|
+
### ./src/elements/ConfiguratorLayout.tsx
|
|
3142
|
+
|
|
3143
|
+
```tsx
|
|
3144
|
+
import { Box } from '@mui/material'
|
|
3145
|
+
|
|
3146
|
+
import { LayoutSlots } from '@salsita-ai/sdk/configurator'
|
|
3147
|
+
|
|
3148
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
3149
|
+
import { ARDENT } from '../_form/index.js'
|
|
3150
|
+
import { DesktopPage } from '../_shared/DesktopPage.js'
|
|
3151
|
+
import { PhonePage } from '../_shared/PhonePage.js'
|
|
3152
|
+
|
|
3153
|
+
// A quote builder with no scene: the layout mounts the services and a page, never `LayoutSlots.SharedCanvas`, so the
|
|
3154
|
+
// configurator loads no 3D scene and the app loader does not wait for one. Desktop or phone follows the width of the
|
|
3155
|
+
// configurator's container.
|
|
3156
|
+
const ConfiguratorLayout = () => {
|
|
3157
|
+
// `null` until the container has been measured
|
|
3158
|
+
const viewports = phx.frontend.useActiveViewports()
|
|
3159
|
+
const isDesktop = viewports?.includes('desktop') ?? false
|
|
3160
|
+
|
|
3161
|
+
return (
|
|
3162
|
+
<Box component="div" sx={{ position: 'absolute', inset: 0, overflow: 'clip', bgcolor: ARDENT.page }}>
|
|
3163
|
+
<LayoutSlots.Services />
|
|
3164
|
+
{viewports !== null && (isDesktop ? <DesktopPage /> : <PhonePage />)}
|
|
3165
|
+
</Box>
|
|
3166
|
+
)
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', ConfiguratorLayout)
|
|
3170
|
+
```
|
|
3171
|
+
|
|
3172
|
+
### ./src/expressions/compressor.ts
|
|
3173
|
+
|
|
3174
|
+
```ts
|
|
3175
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
3176
|
+
|
|
3177
|
+
// The frames Ardent builds, by motor power, with the free air delivery of each at 7.5 bar in m³/min
|
|
3178
|
+
const LARGEST_FRAME = { kw: 250, flow: 41 }
|
|
3179
|
+
const FRAMES = [
|
|
3180
|
+
{ kw: 22, flow: 3.6 },
|
|
3181
|
+
{ kw: 30, flow: 5 },
|
|
3182
|
+
{ kw: 37, flow: 6.3 },
|
|
3183
|
+
{ kw: 45, flow: 7.6 },
|
|
3184
|
+
{ kw: 55, flow: 9.6 },
|
|
3185
|
+
{ kw: 75, flow: 13 },
|
|
3186
|
+
{ kw: 90, flow: 15.8 },
|
|
3187
|
+
{ kw: 110, flow: 19.6 },
|
|
3188
|
+
{ kw: 132, flow: 23 },
|
|
3189
|
+
{ kw: 160, flow: 27.5 },
|
|
3190
|
+
{ kw: 200, flow: 34 },
|
|
3191
|
+
LARGEST_FRAME,
|
|
3192
|
+
]
|
|
3193
|
+
|
|
3194
|
+
// A higher pressure takes more power, so the same frame delivers less air
|
|
3195
|
+
const PRESSURE = {
|
|
3196
|
+
Bar7: { factor: 1, label: '7.5 bar' },
|
|
3197
|
+
Bar10: { factor: 0.85, label: '10 bar' },
|
|
3198
|
+
Bar13: { factor: 0.72, label: '13 bar' },
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
const LARGEST_STOCK_FRAME_KW = 110
|
|
3202
|
+
const RECEIVER_LITRES = { None: 0, Litres500: 500, Litres1000: 1000, Litres2000: 2000 }
|
|
3203
|
+
const RECEIVER_PRICE = { None: 0, Litres500: 1150, Litres1000: 1900, Litres2000: 3400 }
|
|
3204
|
+
const EXPRESS_SURCHARGE = 0.08
|
|
3205
|
+
|
|
3206
|
+
const DRYER_LABEL = { None: 'No dryer', Refrigerant: 'Refrigerant dryer, +3 °C', Desiccant: 'Desiccant dryer' }
|
|
3207
|
+
const DEW_POINT_LABEL = { Minus40: '−40 °C', Minus70: '−70 °C' }
|
|
3208
|
+
const FILTRATION_LABEL = { General: 'General purpose', HighEfficiency: 'High efficiency', OilVapour: 'Oil vapour' }
|
|
3209
|
+
const PLAN_LABEL = { None: 'No plan', Essential: 'Essential, 3 years', TotalCare: 'Total care, 5 years' }
|
|
3210
|
+
|
|
3211
|
+
interface CompressorChoices {
|
|
3212
|
+
readonly flow: number
|
|
3213
|
+
readonly pressure: keyof typeof PRESSURE
|
|
3214
|
+
readonly drive: 'FixedSpeed' | 'VariableSpeed'
|
|
3215
|
+
readonly dryer: keyof typeof DRYER_LABEL
|
|
3216
|
+
readonly cooling: 'Air' | 'Water'
|
|
3217
|
+
readonly enclosure: 'Standard' | 'LowNoise'
|
|
3218
|
+
readonly supply: 'V400Hz50' | 'V480Hz60'
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
// The smallest frame that delivers the flow at the pressure, or the largest one when none does
|
|
3222
|
+
const frameFor = (s: CompressorChoices) => {
|
|
3223
|
+
const factor = PRESSURE[s.pressure].factor
|
|
3224
|
+
|
|
3225
|
+
return FRAMES.find(frame => frame.flow * factor >= s.flow) ?? LARGEST_FRAME
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
const basePriceOf = (kw: number) => {
|
|
3229
|
+
return 6000 + 360 * kw
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
const standardWeeks = (s: CompressorChoices) => {
|
|
3233
|
+
return (
|
|
3234
|
+
(s.drive === 'VariableSpeed' ? 8 : 6) +
|
|
3235
|
+
(s.dryer === 'Desiccant' ? 2 : 0) +
|
|
3236
|
+
(s.cooling === 'Water' ? 1 : 0) +
|
|
3237
|
+
(s.enclosure === 'LowNoise' ? 1 : 0) +
|
|
3238
|
+
(s.supply === 'V480Hz60' ? 1 : 0)
|
|
3239
|
+
)
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
const expressWeeks = (s: CompressorChoices) => {
|
|
3243
|
+
return Math.max(3, standardWeeks(s) - 4)
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
export default phx.domain.implementCustomExpressions({
|
|
3247
|
+
Compressor: {
|
|
3248
|
+
$maxFlow: s => {
|
|
3249
|
+
return Math.floor(LARGEST_FRAME.flow * PRESSURE[s.pressure].factor * 2) / 2
|
|
3250
|
+
},
|
|
3251
|
+
$frameKw: s => {
|
|
3252
|
+
return frameFor(s).kw
|
|
3253
|
+
},
|
|
3254
|
+
$frameName: s => {
|
|
3255
|
+
return `RSV-${frameFor(s).kw}`
|
|
3256
|
+
},
|
|
3257
|
+
// A fixed-speed machine loads and unloads, so it needs a buffer of about 40 litres per m³/min
|
|
3258
|
+
$minReceiverLitres: s => {
|
|
3259
|
+
return s.drive === 'FixedSpeed' ? Math.ceil(s.flow * 40) : 0
|
|
3260
|
+
},
|
|
3261
|
+
$leadTimeWeeks: s => {
|
|
3262
|
+
return s.delivery === 'Express' ? expressWeeks(s) : standardWeeks(s)
|
|
3263
|
+
},
|
|
3264
|
+
// The delivery options name their lead time; express is offered for stock frames only
|
|
3265
|
+
$deliveryOptions: s => {
|
|
3266
|
+
const standard = {
|
|
3267
|
+
label: `Standard, ${standardWeeks(s)} weeks`,
|
|
3268
|
+
description: 'Built to order',
|
|
3269
|
+
value: 'Standard',
|
|
3270
|
+
}
|
|
3271
|
+
const express = {
|
|
3272
|
+
label: `Express, ${expressWeeks(s)} weeks`,
|
|
3273
|
+
description: `From stock, ${EXPRESS_SURCHARGE * 100} % surcharge`,
|
|
3274
|
+
value: 'Express',
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
return frameFor(s).kw > LARGEST_STOCK_FRAME_KW ? [standard] : [standard, express]
|
|
3278
|
+
},
|
|
3279
|
+
$quoteLines: s => {
|
|
3280
|
+
const frame = frameFor(s)
|
|
3281
|
+
const base = basePriceOf(frame.kw)
|
|
3282
|
+
const lines = [
|
|
3283
|
+
{
|
|
3284
|
+
key: 'frame',
|
|
3285
|
+
label: `RSV-${frame.kw} rotary screw compressor`,
|
|
3286
|
+
detail: `${frame.kw} kW, ${PRESSURE[s.pressure].label}, ${(frame.flow * PRESSURE[s.pressure].factor).toFixed(1)} m³/min`,
|
|
3287
|
+
amount: base,
|
|
3288
|
+
},
|
|
3289
|
+
]
|
|
3290
|
+
|
|
3291
|
+
if (s.drive === 'VariableSpeed') {
|
|
3292
|
+
lines.push({
|
|
3293
|
+
key: 'drive',
|
|
3294
|
+
label: 'Variable speed drive',
|
|
3295
|
+
detail: 'Inverter and PM motor',
|
|
3296
|
+
amount: base * 0.28,
|
|
3297
|
+
})
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
if (s.dutyCycle === 'Continuous') {
|
|
3301
|
+
lines.push({ key: 'duty', label: 'Heavy-duty package', detail: 'For continuous duty', amount: base * 0.06 })
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
if (s.dryer === 'Refrigerant') {
|
|
3305
|
+
lines.push({ key: 'dryer', label: 'Refrigerant dryer', detail: '+3 °C dew point', amount: 1200 + 95 * s.flow })
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
if (s.dryer === 'Desiccant') {
|
|
3309
|
+
const dryer = (3800 + 310 * s.flow) * (s.dewPoint === 'Minus70' ? 1.18 : 1)
|
|
3310
|
+
lines.push({
|
|
3311
|
+
key: 'dryer',
|
|
3312
|
+
label: 'Desiccant dryer',
|
|
3313
|
+
detail: `${DEW_POINT_LABEL[s.dewPoint]} dew point`,
|
|
3314
|
+
amount: dryer,
|
|
3315
|
+
})
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
if (s.filtration === 'HighEfficiency') {
|
|
3319
|
+
lines.push({
|
|
3320
|
+
key: 'filters',
|
|
3321
|
+
label: 'High-efficiency filters',
|
|
3322
|
+
detail: '0.01 mg/m³',
|
|
3323
|
+
amount: 1450 + 20 * s.flow,
|
|
3324
|
+
})
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
if (s.filtration === 'OilVapour') {
|
|
3328
|
+
lines.push({ key: 'filters', label: 'Oil-vapour filters', detail: '0.003 mg/m³', amount: 2900 + 40 * s.flow })
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
if (s.cooling === 'Water') {
|
|
3332
|
+
lines.push({ key: 'cooling', label: 'Water cooling', detail: 'Plate heat exchangers', amount: base * 0.07 })
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
if (s.cooling === 'Water' && s.heatRecovery) {
|
|
3336
|
+
lines.push({
|
|
3337
|
+
key: 'heat',
|
|
3338
|
+
label: 'Heat recovery',
|
|
3339
|
+
detail: 'Up to 70 °C hot water',
|
|
3340
|
+
amount: 4200 + 25 * frame.kw,
|
|
3341
|
+
})
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
if (s.receiver !== 'None') {
|
|
3345
|
+
const litres = RECEIVER_LITRES[s.receiver]
|
|
3346
|
+
lines.push({
|
|
3347
|
+
key: 'receiver',
|
|
3348
|
+
label: 'Air receiver',
|
|
3349
|
+
detail: `${litres.toLocaleString('en')} L, 11 bar`,
|
|
3350
|
+
amount: RECEIVER_PRICE[s.receiver],
|
|
3351
|
+
})
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
if (s.enclosure === 'LowNoise') {
|
|
3355
|
+
lines.push({ key: 'canopy', label: 'Low-noise canopy', detail: 'About 66 dB(A)', amount: 2600 + 30 * frame.kw })
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
if (s.supply === 'V480Hz60') {
|
|
3359
|
+
lines.push({ key: 'supply', label: '60 Hz build', detail: '480 V motor and starter', amount: base * 0.04 })
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
if (s.servicePlan !== 'None') {
|
|
3363
|
+
const share = s.servicePlan === 'Essential' ? 0.09 : 0.22
|
|
3364
|
+
lines.push({ key: 'service', label: 'Service plan', detail: PLAN_LABEL[s.servicePlan], amount: base * share })
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
if (s.commissioning) {
|
|
3368
|
+
lines.push({
|
|
3369
|
+
key: 'commissioning',
|
|
3370
|
+
label: 'Commissioning on site',
|
|
3371
|
+
detail: 'One engineer, one day',
|
|
3372
|
+
amount: 1800,
|
|
3373
|
+
})
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
return lines.map(line => ({ ...line, amount: Math.round(line.amount) }))
|
|
3377
|
+
},
|
|
3378
|
+
$subtotal: s => {
|
|
3379
|
+
return s.$quoteLines.reduce((sum, line) => sum + line.amount, 0)
|
|
3380
|
+
},
|
|
3381
|
+
$leadTimeSurcharge: s => {
|
|
3382
|
+
return s.delivery === 'Express' ? Math.round(s.$subtotal * EXPRESS_SURCHARGE) : 0
|
|
3383
|
+
},
|
|
3384
|
+
$total: s => {
|
|
3385
|
+
return s.$subtotal + s.$leadTimeSurcharge
|
|
3386
|
+
},
|
|
3387
|
+
$specSheet: s => {
|
|
3388
|
+
const frame = frameFor(s)
|
|
3389
|
+
|
|
3390
|
+
return [
|
|
3391
|
+
{ label: 'Model', value: `RSV-${frame.kw}, ${frame.kw} kW` },
|
|
3392
|
+
{ label: 'Free air delivery', value: `${s.flow.toFixed(1)} m³/min required` },
|
|
3393
|
+
{ label: 'Working pressure', value: PRESSURE[s.pressure].label },
|
|
3394
|
+
{ label: 'Drive', value: s.drive === 'VariableSpeed' ? 'Variable speed' : 'Fixed speed' },
|
|
3395
|
+
{ label: 'Duty', value: s.dutyCycle === 'Continuous' ? 'Continuous' : 'Intermittent' },
|
|
3396
|
+
{
|
|
3397
|
+
label: 'Dryer',
|
|
3398
|
+
value: s.dryer === 'Desiccant' ? `Desiccant, ${DEW_POINT_LABEL[s.dewPoint]}` : DRYER_LABEL[s.dryer],
|
|
3399
|
+
},
|
|
3400
|
+
{ label: 'Filtration', value: FILTRATION_LABEL[s.filtration] },
|
|
3401
|
+
{
|
|
3402
|
+
label: 'Cooling',
|
|
3403
|
+
value: s.cooling === 'Water' ? `Water${s.heatRecovery ? ', with heat recovery' : ''}` : 'Air',
|
|
3404
|
+
},
|
|
3405
|
+
{
|
|
3406
|
+
label: 'Receiver',
|
|
3407
|
+
value: s.receiver === 'None' ? 'None' : `${RECEIVER_LITRES[s.receiver].toLocaleString('en')} L`,
|
|
3408
|
+
},
|
|
3409
|
+
{ label: 'Canopy', value: s.enclosure === 'LowNoise' ? 'Low-noise' : 'Standard' },
|
|
3410
|
+
{ label: 'Supply', value: s.supply === 'V480Hz60' ? '480 V, 60 Hz' : '400 V, 50 Hz' },
|
|
3411
|
+
{ label: 'Service', value: PLAN_LABEL[s.servicePlan] },
|
|
3412
|
+
{ label: 'Commissioning', value: s.commissioning ? 'On site' : 'Not included' },
|
|
3413
|
+
{ label: 'Delivery', value: `${s.delivery === 'Express' ? 'Express' : 'Standard'}, ${s.$leadTimeWeeks} weeks` },
|
|
3414
|
+
]
|
|
3415
|
+
},
|
|
3416
|
+
},
|
|
3417
|
+
})
|
|
3418
|
+
```
|
|
3419
|
+
|
|
3420
|
+
### ./src/handlers/request-quote.ts
|
|
3421
|
+
|
|
3422
|
+
```ts
|
|
3423
|
+
import { useCallback } from 'react'
|
|
3424
|
+
|
|
3425
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
3426
|
+
import { setQuoteReceipt } from '../_shared/quote-receipt.js'
|
|
3427
|
+
|
|
3428
|
+
const VALID_DAYS = 30
|
|
3429
|
+
|
|
3430
|
+
// Stands in for Ardent's quote service. A deployed project sends the configuration to its finalize webhook here and
|
|
3431
|
+
// waits for the answer; this one answers after a moment with a fixed engineer and a reference made from the
|
|
3432
|
+
// configuration's id, then enters the `requested` navigation state.
|
|
3433
|
+
export default phx.frontend.createPhoenixHandler('requestQuote', () => {
|
|
3434
|
+
const navigate = phx.frontend.useNavigate()
|
|
3435
|
+
const configurationId = phx.frontend.useConfigurationId()
|
|
3436
|
+
const total = phx.frontend.useRootEntityPrice()
|
|
3437
|
+
|
|
3438
|
+
return {
|
|
3439
|
+
handler: useCallback(async () => {
|
|
3440
|
+
await new Promise(resolve => setTimeout(resolve, 1600))
|
|
3441
|
+
|
|
3442
|
+
const validUntil = new Date(Date.now() + VALID_DAYS * 24 * 60 * 60 * 1000)
|
|
3443
|
+
|
|
3444
|
+
setQuoteReceipt({
|
|
3445
|
+
reference: `AQ-${String(configurationId)
|
|
3446
|
+
.replace(/[^a-z0-9]/gi, '')
|
|
3447
|
+
.slice(-6)
|
|
3448
|
+
.toUpperCase()}`,
|
|
3449
|
+
engineer: 'Marta Kovács, application engineer',
|
|
3450
|
+
validUntil: validUntil.toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }),
|
|
3451
|
+
total: total ?? '',
|
|
3452
|
+
})
|
|
3453
|
+
navigate('requested')
|
|
3454
|
+
}, [configurationId, navigate, total]),
|
|
3455
|
+
}
|
|
3456
|
+
})
|
|
3457
|
+
```
|
|
3458
|
+
|
|
3459
|
+
### ./src/invariants/compressor.ts
|
|
3460
|
+
|
|
3461
|
+
```ts
|
|
3462
|
+
import { phx } from '../../.phoenix/phx.js'
|
|
3463
|
+
|
|
3464
|
+
const PRESSURE_LABEL = { Bar7: '7.5 bar', Bar10: '10 bar', Bar13: '13 bar' }
|
|
3465
|
+
const RECEIVER_LITRES = { None: 0, Litres500: 500, Litres1000: 1000, Litres2000: 2000 }
|
|
3466
|
+
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/
|
|
3467
|
+
|
|
3468
|
+
// Each rule is a `warning` in the spec: a change that breaks it is kept, so the customer can fix the conflict where it
|
|
3469
|
+
// is shown, and finalizing waits until no warning is left
|
|
3470
|
+
export default phx.domain.implementCustomInvariants({
|
|
3471
|
+
Compressor: {
|
|
3472
|
+
// The largest frame delivers less air at a higher pressure, so the limit depends on an earlier choice
|
|
3473
|
+
flowRange: s => {
|
|
3474
|
+
if (s.flow <= s.$maxFlow) {
|
|
3475
|
+
return []
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
return [
|
|
3479
|
+
{
|
|
3480
|
+
type: 'flowAboveRange',
|
|
3481
|
+
severity: 'warning',
|
|
3482
|
+
message: `At ${PRESSURE_LABEL[s.pressure]} the largest frame delivers ${s.$maxFlow.toFixed(1)} m³/min. Lower the flow or the pressure.`,
|
|
3483
|
+
},
|
|
3484
|
+
]
|
|
3485
|
+
},
|
|
3486
|
+
desiccantNeedsFiltration: s => {
|
|
3487
|
+
return s.dryer !== 'Desiccant' || s.filtration !== 'General'
|
|
3488
|
+
},
|
|
3489
|
+
// The message names the size the flow needs, so it is built here rather than in the spec
|
|
3490
|
+
receiverSize: s => {
|
|
3491
|
+
if (RECEIVER_LITRES[s.receiver] >= s.$minReceiverLitres) {
|
|
3492
|
+
return []
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
return [
|
|
3496
|
+
{
|
|
3497
|
+
type: 'receiverTooSmall',
|
|
3498
|
+
severity: 'warning',
|
|
3499
|
+
message: `A fixed-speed compressor at ${s.flow.toFixed(1)} m³/min needs a receiver of at least ${s.$minReceiverLitres.toLocaleString('en')} L.`,
|
|
3500
|
+
},
|
|
3501
|
+
]
|
|
3502
|
+
},
|
|
3503
|
+
expressUnavailable: s => {
|
|
3504
|
+
return s.delivery !== 'Express' || s.$frameKw <= 110
|
|
3505
|
+
},
|
|
3506
|
+
companyMissing: s => {
|
|
3507
|
+
return s.company.trim().length > 0
|
|
3508
|
+
},
|
|
3509
|
+
emailInvalid: s => {
|
|
3510
|
+
return EMAIL.test(s.email.trim())
|
|
3511
|
+
},
|
|
3512
|
+
},
|
|
3513
|
+
})
|
|
3514
|
+
```
|
|
3515
|
+
|
|
3516
|
+
</details>
|