@softov/scena 0.1.2
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/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/controls/command.d.ts +18 -0
- package/dist/controls/command.js +150 -0
- package/dist/controls/events.d.ts +3 -0
- package/dist/controls/events.js +43 -0
- package/dist/controls/index.d.ts +10 -0
- package/dist/controls/index.js +10 -0
- package/dist/controls/keybinding.d.ts +9 -0
- package/dist/controls/keybinding.js +94 -0
- package/dist/controls/layout.d.ts +16 -0
- package/dist/controls/layout.js +201 -0
- package/dist/controls/manifest.d.ts +21 -0
- package/dist/controls/manifest.js +219 -0
- package/dist/controls/session.d.ts +16 -0
- package/dist/controls/session.js +95 -0
- package/dist/controls/shell.d.ts +8 -0
- package/dist/controls/shell.js +48 -0
- package/dist/controls/storage/layout-local.d.ts +6 -0
- package/dist/controls/storage/layout-local.js +47 -0
- package/dist/controls/storage/session-local.d.ts +5 -0
- package/dist/controls/storage/session-local.js +38 -0
- package/dist/controls/when.d.ts +7 -0
- package/dist/controls/when.js +226 -0
- package/dist/converters/a2ui-v0.10.d.ts +23 -0
- package/dist/converters/a2ui-v0.10.js +136 -0
- package/dist/converters/index.d.ts +1 -0
- package/dist/converters/index.js +1 -0
- package/dist/core/backends/modus-backend.d.ts +13 -0
- package/dist/core/backends/modus-backend.js +115 -0
- package/dist/core/backends/storage-backend.d.ts +7 -0
- package/dist/core/backends/storage-backend.js +73 -0
- package/dist/core/backends/yjs-backend.d.ts +3 -0
- package/dist/core/backends/yjs-backend.js +44 -0
- package/dist/core/binding-resolver.d.ts +7 -0
- package/dist/core/binding-resolver.js +106 -0
- package/dist/core/component-graph.d.ts +6 -0
- package/dist/core/component-graph.js +77 -0
- package/dist/core/component-registry.d.ts +7 -0
- package/dist/core/component-registry.js +87 -0
- package/dist/core/computed-dsl.d.ts +2 -0
- package/dist/core/computed-dsl.js +267 -0
- package/dist/core/converter-registry.d.ts +2 -0
- package/dist/core/converter-registry.js +37 -0
- package/dist/core/disposable.d.ts +3 -0
- package/dist/core/disposable.js +19 -0
- package/dist/core/dynamic-resolver.d.ts +12 -0
- package/dist/core/dynamic-resolver.js +134 -0
- package/dist/core/index.d.ts +44 -0
- package/dist/core/index.js +48 -0
- package/dist/core/label.d.ts +2 -0
- package/dist/core/label.js +14 -0
- package/dist/core/mount-menu.d.ts +2 -0
- package/dist/core/mount-menu.js +23 -0
- package/dist/core/mount-surface.d.ts +15 -0
- package/dist/core/mount-surface.js +214 -0
- package/dist/core/path-resolver.d.ts +14 -0
- package/dist/core/path-resolver.js +169 -0
- package/dist/core/permissions.d.ts +2 -0
- package/dist/core/permissions.js +50 -0
- package/dist/core/reactive-store.d.ts +17 -0
- package/dist/core/reactive-store.js +496 -0
- package/dist/core/scena.d.ts +2 -0
- package/dist/core/scena.js +115 -0
- package/dist/core/surface-presentation.d.ts +6 -0
- package/dist/core/surface-presentation.js +12 -0
- package/dist/i18n/i18n-backend.d.ts +2 -0
- package/dist/i18n/i18n-backend.js +35 -0
- package/dist/i18n/registry.d.ts +32 -0
- package/dist/i18n/registry.js +200 -0
- package/dist/porta/Limen.css +50 -0
- package/dist/porta/Limen.d.ts +16 -0
- package/dist/porta/Limen.js +14 -0
- package/dist/porta/PortaContext.d.ts +10 -0
- package/dist/porta/PortaContext.js +20 -0
- package/dist/porta/PortaLock.css +14 -0
- package/dist/porta/PortaLock.d.ts +10 -0
- package/dist/porta/PortaLock.js +13 -0
- package/dist/porta/SigillumGate.d.ts +8 -0
- package/dist/porta/SigillumGate.js +7 -0
- package/dist/porta/commands.d.ts +3 -0
- package/dist/porta/commands.js +125 -0
- package/dist/porta/index.d.ts +18 -0
- package/dist/porta/index.js +22 -0
- package/dist/porta/login-form.block.d.ts +2 -0
- package/dist/porta/login-form.block.js +7 -0
- package/dist/porta/match.d.ts +2 -0
- package/dist/porta/match.js +20 -0
- package/dist/porta/provider.d.ts +59 -0
- package/dist/porta/provider.js +1 -0
- package/dist/porta/providers/examples/google-oauth.d.ts +6 -0
- package/dist/porta/providers/examples/google-oauth.js +21 -0
- package/dist/porta/providers/examples/index.d.ts +6 -0
- package/dist/porta/providers/examples/index.js +3 -0
- package/dist/porta/providers/examples/magic-link.d.ts +8 -0
- package/dist/porta/providers/examples/magic-link.js +31 -0
- package/dist/porta/providers/examples/sms-otp.d.ts +9 -0
- package/dist/porta/providers/examples/sms-otp.js +31 -0
- package/dist/porta/providers/password.d.ts +12 -0
- package/dist/porta/providers/password.js +46 -0
- package/dist/porta/register-blocks.d.ts +3 -0
- package/dist/porta/register-blocks.js +31 -0
- package/dist/porta/register.d.ts +15 -0
- package/dist/porta/register.js +31 -0
- package/dist/porta/sigillum.d.ts +19 -0
- package/dist/porta/sigillum.js +41 -0
- package/dist/react/DefaultShell.d.ts +1 -0
- package/dist/react/DefaultShell.js +37 -0
- package/dist/react/Scena.d.ts +9 -0
- package/dist/react/Scena.js +20 -0
- package/dist/react/ScenaProvider.d.ts +8 -0
- package/dist/react/ScenaProvider.js +13 -0
- package/dist/react/ShellSplitter.d.ts +13 -0
- package/dist/react/ShellSplitter.js +88 -0
- package/dist/react/SurfaceArea.d.ts +13 -0
- package/dist/react/SurfaceArea.js +75 -0
- package/dist/react/Translate.d.ts +8 -0
- package/dist/react/Translate.js +10 -0
- package/dist/react/ViewMount.d.ts +12 -0
- package/dist/react/ViewMount.js +268 -0
- package/dist/react/core.d.ts +6 -0
- package/dist/react/core.js +10 -0
- package/dist/react/hooks/useCommand.d.ts +1 -0
- package/dist/react/hooks/useCommand.js +6 -0
- package/dist/react/hooks/useI18n.d.ts +9 -0
- package/dist/react/hooks/useI18n.js +16 -0
- package/dist/react/hooks/useLabel.d.ts +2 -0
- package/dist/react/hooks/useLabel.js +19 -0
- package/dist/react/hooks/useLayout.d.ts +2 -0
- package/dist/react/hooks/useLayout.js +10 -0
- package/dist/react/hooks/useMounts.d.ts +2 -0
- package/dist/react/hooks/useMounts.js +17 -0
- package/dist/react/hooks/useStore.d.ts +3 -0
- package/dist/react/hooks/useStore.js +40 -0
- package/dist/react/hooks/useStoreLabel.d.ts +3 -0
- package/dist/react/hooks/useStoreLabel.js +25 -0
- package/dist/react/hooks/useSurfacePresentation.d.ts +4 -0
- package/dist/react/hooks/useSurfacePresentation.js +16 -0
- package/dist/react/index.d.ts +20 -0
- package/dist/react/index.js +20 -0
- package/dist/react/mount-context.d.ts +8 -0
- package/dist/react/mount-context.js +20 -0
- package/dist/react/useSurfaceResize.d.ts +39 -0
- package/dist/react/useSurfaceResize.js +135 -0
- package/dist/runtime/args-schema.d.ts +11 -0
- package/dist/runtime/args-schema.js +45 -0
- package/dist/runtime/builtins.d.ts +3 -0
- package/dist/runtime/builtins.js +193 -0
- package/dist/runtime/client-handlers.d.ts +3 -0
- package/dist/runtime/client-handlers.js +19 -0
- package/dist/runtime/in-memory-socket.d.ts +7 -0
- package/dist/runtime/in-memory-socket.js +53 -0
- package/dist/runtime/index.d.ts +10 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/layout-commands.d.ts +5 -0
- package/dist/runtime/layout-commands.js +548 -0
- package/dist/runtime/opener-commands.d.ts +17 -0
- package/dist/runtime/opener-commands.js +96 -0
- package/dist/runtime/surface-bridge.d.ts +15 -0
- package/dist/runtime/surface-bridge.js +116 -0
- package/dist/styles/bar.css +155 -0
- package/dist/styles/base.css +285 -0
- package/dist/styles/geometry.css +32 -0
- package/dist/styles/index.d.ts +29 -0
- package/dist/styles/index.js +109 -0
- package/dist/styles/scrollbar.css +45 -0
- package/dist/styles/surface.css +179 -0
- package/dist/styles/theme.css +3 -0
- package/dist/styles/themes/dark.css +30 -0
- package/dist/styles/themes/light.css +1 -0
- package/dist/styles/themes/tokens.css +222 -0
- package/dist/types/binding-resolver.d.ts +30 -0
- package/dist/types/binding-resolver.js +1 -0
- package/dist/types/colors.d.ts +7 -0
- package/dist/types/colors.js +59 -0
- package/dist/types/command.d.ts +62 -0
- package/dist/types/command.js +1 -0
- package/dist/types/component-graph.d.ts +72 -0
- package/dist/types/component-graph.js +40 -0
- package/dist/types/component-registry.d.ts +70 -0
- package/dist/types/component-registry.js +1 -0
- package/dist/types/converter-registry.d.ts +17 -0
- package/dist/types/converter-registry.js +1 -0
- package/dist/types/disposable.d.ts +3 -0
- package/dist/types/disposable.js +1 -0
- package/dist/types/events.d.ts +75 -0
- package/dist/types/events.js +1 -0
- package/dist/types/host.d.ts +50 -0
- package/dist/types/host.js +1 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/index.js +23 -0
- package/dist/types/keybinding.d.ts +36 -0
- package/dist/types/keybinding.js +1 -0
- package/dist/types/label.d.ts +9 -0
- package/dist/types/label.js +1 -0
- package/dist/types/layout.d.ts +117 -0
- package/dist/types/layout.js +1 -0
- package/dist/types/manifest.d.ts +152 -0
- package/dist/types/manifest.js +1 -0
- package/dist/types/mount-menu.d.ts +8 -0
- package/dist/types/mount-menu.js +1 -0
- package/dist/types/mount-surface.d.ts +87 -0
- package/dist/types/mount-surface.js +1 -0
- package/dist/types/permissions.d.ts +18 -0
- package/dist/types/permissions.js +1 -0
- package/dist/types/query.d.ts +34 -0
- package/dist/types/query.js +7 -0
- package/dist/types/reactive-store.d.ts +39 -0
- package/dist/types/reactive-store.js +1 -0
- package/dist/types/scena.d.ts +53 -0
- package/dist/types/scena.js +1 -0
- package/dist/types/scope-backend.d.ts +16 -0
- package/dist/types/scope-backend.js +1 -0
- package/dist/types/session.d.ts +39 -0
- package/dist/types/session.js +1 -0
- package/dist/types/shell.d.ts +21 -0
- package/dist/types/shell.js +1 -0
- package/dist/types/when.d.ts +10 -0
- package/dist/types/when.js +1 -0
- package/dist/ui/_utils.d.ts +8 -0
- package/dist/ui/_utils.js +53 -0
- package/dist/ui/campus/CampusMappa.d.ts +18 -0
- package/dist/ui/campus/CampusMappa.js +106 -0
- package/dist/ui/campus/CampusNodus.d.ts +25 -0
- package/dist/ui/campus/CampusNodus.js +211 -0
- package/dist/ui/campus/CampusStratum.d.ts +13 -0
- package/dist/ui/campus/CampusStratum.js +20 -0
- package/dist/ui/campus/CampusVelum.d.ts +7 -0
- package/dist/ui/campus/CampusVelum.js +12 -0
- package/dist/ui/campus/CampusView.d.ts +27 -0
- package/dist/ui/campus/CampusView.js +420 -0
- package/dist/ui/campus/context.d.ts +13 -0
- package/dist/ui/campus/context.js +12 -0
- package/dist/ui/campus/index.d.ts +14 -0
- package/dist/ui/campus/index.js +11 -0
- package/dist/ui/campus/stratum-context.d.ts +2 -0
- package/dist/ui/campus/stratum-context.js +10 -0
- package/dist/ui/campus/types.d.ts +47 -0
- package/dist/ui/campus/types.js +4 -0
- package/dist/ui/chart/BarChart.css +53 -0
- package/dist/ui/chart/BarChart.d.ts +24 -0
- package/dist/ui/chart/BarChart.js +32 -0
- package/dist/ui/chart/Donut.css +15 -0
- package/dist/ui/chart/Donut.d.ts +15 -0
- package/dist/ui/chart/Donut.js +18 -0
- package/dist/ui/chart/LineChart.css +8 -0
- package/dist/ui/chart/LineChart.d.ts +24 -0
- package/dist/ui/chart/LineChart.js +29 -0
- package/dist/ui/chart/catalog.d.ts +2 -0
- package/dist/ui/chart/catalog.js +7 -0
- package/dist/ui/chart/index.d.ts +6 -0
- package/dist/ui/chart/index.js +4 -0
- package/dist/ui/control/Button.css +7 -0
- package/dist/ui/control/Button.d.ts +17 -0
- package/dist/ui/control/Button.js +17 -0
- package/dist/ui/control/CheckBox.css +16 -0
- package/dist/ui/control/CheckBox.d.ts +15 -0
- package/dist/ui/control/CheckBox.js +23 -0
- package/dist/ui/control/Checks.css +6 -0
- package/dist/ui/control/Checks.d.ts +9 -0
- package/dist/ui/control/Checks.js +10 -0
- package/dist/ui/control/ChoicePicker.css +16 -0
- package/dist/ui/control/ChoicePicker.d.ts +25 -0
- package/dist/ui/control/ChoicePicker.js +38 -0
- package/dist/ui/control/DateTimeInput.css +1 -0
- package/dist/ui/control/DateTimeInput.d.ts +22 -0
- package/dist/ui/control/DateTimeInput.js +41 -0
- package/dist/ui/control/LocaleToggle.css +38 -0
- package/dist/ui/control/LocaleToggle.d.ts +11 -0
- package/dist/ui/control/LocaleToggle.js +23 -0
- package/dist/ui/control/ReloadButton.d.ts +6 -0
- package/dist/ui/control/ReloadButton.js +9 -0
- package/dist/ui/control/Slider.css +2 -0
- package/dist/ui/control/Slider.d.ts +21 -0
- package/dist/ui/control/Slider.js +37 -0
- package/dist/ui/control/TextField.css +0 -0
- package/dist/ui/control/TextField.d.ts +25 -0
- package/dist/ui/control/TextField.js +36 -0
- package/dist/ui/control/_field-parts.css +102 -0
- package/dist/ui/control/_field-parts.d.ts +15 -0
- package/dist/ui/control/_field-parts.js +17 -0
- package/dist/ui/control/catalog.d.ts +2 -0
- package/dist/ui/control/catalog.js +10 -0
- package/dist/ui/control/index.d.ts +13 -0
- package/dist/ui/control/index.js +11 -0
- package/dist/ui/data/DataTable.d.ts +19 -0
- package/dist/ui/data/DataTable.js +20 -0
- package/dist/ui/data/Filter.d.ts +13 -0
- package/dist/ui/data/Filter.js +56 -0
- package/dist/ui/data/List.css +16 -0
- package/dist/ui/data/List.d.ts +15 -0
- package/dist/ui/data/List.js +18 -0
- package/dist/ui/data/Listable.css +245 -0
- package/dist/ui/data/Listable.d.ts +60 -0
- package/dist/ui/data/Listable.js +209 -0
- package/dist/ui/data/Pagination.d.ts +5 -0
- package/dist/ui/data/Pagination.js +43 -0
- package/dist/ui/data/Tree.css +118 -0
- package/dist/ui/data/Tree.d.ts +45 -0
- package/dist/ui/data/Tree.js +170 -0
- package/dist/ui/data/catalog.d.ts +2 -0
- package/dist/ui/data/catalog.js +7 -0
- package/dist/ui/data/index.d.ts +11 -0
- package/dist/ui/data/index.js +7 -0
- package/dist/ui/display/AddPlaceholder.d.ts +6 -0
- package/dist/ui/display/AddPlaceholder.js +24 -0
- package/dist/ui/display/Alert.css +42 -0
- package/dist/ui/display/Alert.d.ts +14 -0
- package/dist/ui/display/Alert.js +13 -0
- package/dist/ui/display/Avatar.css +19 -0
- package/dist/ui/display/Avatar.d.ts +16 -0
- package/dist/ui/display/Avatar.js +52 -0
- package/dist/ui/display/Badge.css +26 -0
- package/dist/ui/display/Badge.d.ts +16 -0
- package/dist/ui/display/Badge.js +9 -0
- package/dist/ui/display/Card.css +15 -0
- package/dist/ui/display/Card.d.ts +11 -0
- package/dist/ui/display/Card.js +8 -0
- package/dist/ui/display/DetailContainer.d.ts +6 -0
- package/dist/ui/display/DetailContainer.js +7 -0
- package/dist/ui/display/DetailHeader.css +53 -0
- package/dist/ui/display/DetailHeader.d.ts +22 -0
- package/dist/ui/display/DetailHeader.js +8 -0
- package/dist/ui/display/DetailList.css +26 -0
- package/dist/ui/display/DetailList.d.ts +13 -0
- package/dist/ui/display/DetailList.js +5 -0
- package/dist/ui/display/DetailNotFound.d.ts +17 -0
- package/dist/ui/display/DetailNotFound.js +39 -0
- package/dist/ui/display/Divider.css +6 -0
- package/dist/ui/display/Divider.d.ts +8 -0
- package/dist/ui/display/Divider.js +16 -0
- package/dist/ui/display/Divisor.css +30 -0
- package/dist/ui/display/Divisor.d.ts +11 -0
- package/dist/ui/display/Divisor.js +43 -0
- package/dist/ui/display/Icon.css +10 -0
- package/dist/ui/display/Icon.d.ts +14 -0
- package/dist/ui/display/Icon.js +144 -0
- package/dist/ui/display/Image.css +1 -0
- package/dist/ui/display/Image.d.ts +16 -0
- package/dist/ui/display/Image.js +19 -0
- package/dist/ui/display/Markdown.css +38 -0
- package/dist/ui/display/Markdown.d.ts +8 -0
- package/dist/ui/display/Markdown.js +140 -0
- package/dist/ui/display/Progress.css +39 -0
- package/dist/ui/display/Progress.d.ts +15 -0
- package/dist/ui/display/Progress.js +17 -0
- package/dist/ui/display/SectionTitle.d.ts +6 -0
- package/dist/ui/display/SectionTitle.js +6 -0
- package/dist/ui/display/Skeleton.css +20 -0
- package/dist/ui/display/Skeleton.d.ts +11 -0
- package/dist/ui/display/Skeleton.js +11 -0
- package/dist/ui/display/Spinner.css +20 -0
- package/dist/ui/display/Spinner.d.ts +12 -0
- package/dist/ui/display/Spinner.js +12 -0
- package/dist/ui/display/Svg.d.ts +13 -0
- package/dist/ui/display/Svg.js +17 -0
- package/dist/ui/display/Text.css +20 -0
- package/dist/ui/display/Text.d.ts +13 -0
- package/dist/ui/display/Text.js +15 -0
- package/dist/ui/display/catalog.d.ts +2 -0
- package/dist/ui/display/catalog.js +12 -0
- package/dist/ui/display/index.d.ts +22 -0
- package/dist/ui/display/index.js +14 -0
- package/dist/ui/embed/HtmlEmbed.d.ts +12 -0
- package/dist/ui/embed/HtmlEmbed.js +16 -0
- package/dist/ui/embed/index.d.ts +2 -0
- package/dist/ui/embed/index.js +1 -0
- package/dist/ui/forms/DangerZone.d.ts +8 -0
- package/dist/ui/forms/DangerZone.js +31 -0
- package/dist/ui/forms/Field.css +15 -0
- package/dist/ui/forms/Field.d.ts +28 -0
- package/dist/ui/forms/Field.js +25 -0
- package/dist/ui/forms/FieldGroup.css +15 -0
- package/dist/ui/forms/FieldGroup.d.ts +7 -0
- package/dist/ui/forms/FieldGroup.js +7 -0
- package/dist/ui/forms/Form.css +14 -0
- package/dist/ui/forms/Form.d.ts +18 -0
- package/dist/ui/forms/Form.js +17 -0
- package/dist/ui/forms/FormActions.css +11 -0
- package/dist/ui/forms/FormActions.d.ts +18 -0
- package/dist/ui/forms/FormActions.js +10 -0
- package/dist/ui/forms/FormSection.css +19 -0
- package/dist/ui/forms/FormSection.d.ts +7 -0
- package/dist/ui/forms/FormSection.js +7 -0
- package/dist/ui/forms/LoginForm.css +105 -0
- package/dist/ui/forms/LoginForm.d.ts +12 -0
- package/dist/ui/forms/LoginForm.js +137 -0
- package/dist/ui/forms/SchemaForm.css +81 -0
- package/dist/ui/forms/SchemaForm.d.ts +26 -0
- package/dist/ui/forms/SchemaForm.js +186 -0
- package/dist/ui/forms/SettingsContainer.d.ts +8 -0
- package/dist/ui/forms/SettingsContainer.js +8 -0
- package/dist/ui/forms/catalog.d.ts +2 -0
- package/dist/ui/forms/catalog.js +11 -0
- package/dist/ui/forms/index.d.ts +19 -0
- package/dist/ui/forms/index.js +12 -0
- package/dist/ui/forms/schema-walk.d.ts +50 -0
- package/dist/ui/forms/schema-walk.js +116 -0
- package/dist/ui/index.d.ts +58 -0
- package/dist/ui/index.js +63 -0
- package/dist/ui/layout/BarLayout.d.ts +2 -0
- package/dist/ui/layout/BarLayout.js +18 -0
- package/dist/ui/layout/Column.css +6 -0
- package/dist/ui/layout/Column.d.ts +12 -0
- package/dist/ui/layout/Column.js +13 -0
- package/dist/ui/layout/FloatingLayout.d.ts +2 -0
- package/dist/ui/layout/FloatingLayout.js +12 -0
- package/dist/ui/layout/Grid.css +5 -0
- package/dist/ui/layout/Grid.d.ts +13 -0
- package/dist/ui/layout/Grid.js +19 -0
- package/dist/ui/layout/InlineLayout.d.ts +2 -0
- package/dist/ui/layout/InlineLayout.js +13 -0
- package/dist/ui/layout/MountTitle.d.ts +12 -0
- package/dist/ui/layout/MountTitle.js +42 -0
- package/dist/ui/layout/RailLayout.d.ts +2 -0
- package/dist/ui/layout/RailLayout.js +38 -0
- package/dist/ui/layout/Row.css +16 -0
- package/dist/ui/layout/Row.d.ts +13 -0
- package/dist/ui/layout/Row.js +21 -0
- package/dist/ui/layout/SectionHeader.d.ts +10 -0
- package/dist/ui/layout/SectionHeader.js +65 -0
- package/dist/ui/layout/SingleLayout.d.ts +2 -0
- package/dist/ui/layout/SingleLayout.js +25 -0
- package/dist/ui/layout/SpatialCard.d.ts +9 -0
- package/dist/ui/layout/SpatialCard.js +30 -0
- package/dist/ui/layout/SpatialLayout.d.ts +2 -0
- package/dist/ui/layout/SpatialLayout.js +446 -0
- package/dist/ui/layout/SplitLayout.d.ts +2 -0
- package/dist/ui/layout/SplitLayout.js +159 -0
- package/dist/ui/layout/Splitter.d.ts +12 -0
- package/dist/ui/layout/Splitter.js +64 -0
- package/dist/ui/layout/StackHeader.d.ts +9 -0
- package/dist/ui/layout/StackHeader.js +87 -0
- package/dist/ui/layout/StackLayout.d.ts +2 -0
- package/dist/ui/layout/StackLayout.js +137 -0
- package/dist/ui/layout/TabLayout.d.ts +2 -0
- package/dist/ui/layout/TabLayout.js +145 -0
- package/dist/ui/layout/TabPanelLayout.d.ts +20 -0
- package/dist/ui/layout/TabPanelLayout.js +615 -0
- package/dist/ui/layout/catalog.d.ts +2 -0
- package/dist/ui/layout/catalog.js +10 -0
- package/dist/ui/layout/index.d.ts +17 -0
- package/dist/ui/layout/index.js +17 -0
- package/dist/ui/layout/register.d.ts +3 -0
- package/dist/ui/layout/register.js +25 -0
- package/dist/ui/media/Audio.css +1 -0
- package/dist/ui/media/Audio.d.ts +12 -0
- package/dist/ui/media/Audio.js +8 -0
- package/dist/ui/media/Video.css +1 -0
- package/dist/ui/media/Video.d.ts +16 -0
- package/dist/ui/media/Video.js +9 -0
- package/dist/ui/media/catalog.d.ts +2 -0
- package/dist/ui/media/catalog.js +5 -0
- package/dist/ui/media/index.d.ts +2 -0
- package/dist/ui/media/index.js +3 -0
- package/dist/ui/menu/ActionList.css +262 -0
- package/dist/ui/menu/ActionList.d.ts +19 -0
- package/dist/ui/menu/ActionList.js +369 -0
- package/dist/ui/menu/ContainerMenu.d.ts +13 -0
- package/dist/ui/menu/ContainerMenu.js +57 -0
- package/dist/ui/menu/ContextMenu.d.ts +27 -0
- package/dist/ui/menu/ContextMenu.js +93 -0
- package/dist/ui/menu/index.d.ts +10 -0
- package/dist/ui/menu/index.js +7 -0
- package/dist/ui/menu/token.d.ts +22 -0
- package/dist/ui/menu/token.js +145 -0
- package/dist/ui/menu/useChatPicker.d.ts +24 -0
- package/dist/ui/menu/useChatPicker.js +210 -0
- package/dist/ui/navigation/Breadcrumb.css +30 -0
- package/dist/ui/navigation/Breadcrumb.d.ts +14 -0
- package/dist/ui/navigation/Breadcrumb.js +12 -0
- package/dist/ui/navigation/Link.css +13 -0
- package/dist/ui/navigation/Link.d.ts +18 -0
- package/dist/ui/navigation/Link.js +43 -0
- package/dist/ui/navigation/Tabs.css +39 -0
- package/dist/ui/navigation/Tabs.d.ts +18 -0
- package/dist/ui/navigation/Tabs.js +55 -0
- package/dist/ui/navigation/Toolbar.css +25 -0
- package/dist/ui/navigation/Toolbar.d.ts +12 -0
- package/dist/ui/navigation/Toolbar.js +21 -0
- package/dist/ui/navigation/catalog.d.ts +2 -0
- package/dist/ui/navigation/catalog.js +5 -0
- package/dist/ui/navigation/index.d.ts +8 -0
- package/dist/ui/navigation/index.js +5 -0
- package/dist/ui/overlay/Modal.css +35 -0
- package/dist/ui/overlay/Modal.d.ts +12 -0
- package/dist/ui/overlay/Modal.js +57 -0
- package/dist/ui/overlay/Popup.d.ts +12 -0
- package/dist/ui/overlay/Popup.js +74 -0
- package/dist/ui/overlay/catalog.d.ts +2 -0
- package/dist/ui/overlay/catalog.js +4 -0
- package/dist/ui/overlay/index.d.ts +1 -0
- package/dist/ui/overlay/index.js +2 -0
- package/dist/ui/register.d.ts +6 -0
- package/dist/ui/register.js +26 -0
- package/package.json +115 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState, } from 'react';
|
|
3
|
+
import { useScena } from '../../react/ScenaProvider.js';
|
|
4
|
+
import { ContextMenu } from '../menu/ContextMenu.js';
|
|
5
|
+
import { Divisor } from '../display/Divisor.js';
|
|
6
|
+
import { resolveColorRgb } from '../../types/colors.js';
|
|
7
|
+
import { NAMED_GLYPH } from '../display/Icon.js';
|
|
8
|
+
import { useStoreLabel } from '../../react/hooks/useStoreLabel.js';
|
|
9
|
+
// Multi-group tabs. Recursive binary tree: a leaf is one Tab strip; a split
|
|
10
|
+
// holds two children with a draggable divider. Drag a tab onto another
|
|
11
|
+
// leaf's 5 zones to either join (center) or split (top/right/bottom/left).
|
|
12
|
+
// Each leaf has a top-right [...] menu for group-level actions.
|
|
13
|
+
const DRAG_MIME = 'application/x-scena-tab-panel';
|
|
14
|
+
const EDGE_THRESHOLD = 0.2;
|
|
15
|
+
const MIN_RATIO = 0.05;
|
|
16
|
+
// ----- Tree helpers (pure) -----
|
|
17
|
+
function newLeafId() {
|
|
18
|
+
// Random suffix — the previous `g${counter}` scheme collided with persisted
|
|
19
|
+
// tree leaf ids after a reload (the counter resets, localStorage doesn't),
|
|
20
|
+
// producing two leaves with the same id. `replaceLeaf` then mutated both,
|
|
21
|
+
// and `mapLeaves(... ? null : l)` dropped both, collapsing whole splits.
|
|
22
|
+
return `g_${Math.random().toString(36).slice(2, 10)}`;
|
|
23
|
+
}
|
|
24
|
+
function makeLeaf(tabs, activeKey) {
|
|
25
|
+
return { kind: 'leaf', id: newLeafId(), tabs, activeKey };
|
|
26
|
+
}
|
|
27
|
+
function collectTabs(node, out) {
|
|
28
|
+
if (node.kind === 'leaf') {
|
|
29
|
+
for (const k of node.tabs)
|
|
30
|
+
out.add(k);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
collectTabs(node.first, out);
|
|
34
|
+
collectTabs(node.second, out);
|
|
35
|
+
}
|
|
36
|
+
function collectLeaves(node, out) {
|
|
37
|
+
if (node.kind === 'leaf') {
|
|
38
|
+
out.push(node);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
collectLeaves(node.first, out);
|
|
42
|
+
collectLeaves(node.second, out);
|
|
43
|
+
}
|
|
44
|
+
// Strip dead tabs from each leaf. Empty leaves are KEPT — a leaf only goes
|
|
45
|
+
// away through explicit user action (drag-last-tab-out via `removeTab`, or
|
|
46
|
+
// "Close group" via the leaf menu). That's what makes "Split group right"
|
|
47
|
+
// usable: the freshly-created sibling leaf is empty by design and must
|
|
48
|
+
// survive the next render so the user can drop a tab into it.
|
|
49
|
+
function pruneTree(node, alive) {
|
|
50
|
+
if (node.kind === 'leaf') {
|
|
51
|
+
const tabs = node.tabs.filter((k) => alive.has(k));
|
|
52
|
+
const activeKey = node.activeKey && alive.has(node.activeKey)
|
|
53
|
+
? node.activeKey
|
|
54
|
+
: tabs[0];
|
|
55
|
+
return { ...node, tabs, activeKey };
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
...node,
|
|
59
|
+
first: pruneTree(node.first, alive),
|
|
60
|
+
second: pruneTree(node.second, alive),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// Map across leaves; if `fn` returns null, the leaf is dropped (parent
|
|
64
|
+
// collapses to surviving sibling). Returns null if every leaf dropped.
|
|
65
|
+
function mapLeaves(node, fn) {
|
|
66
|
+
if (node.kind === 'leaf')
|
|
67
|
+
return fn(node);
|
|
68
|
+
const first = mapLeaves(node.first, fn);
|
|
69
|
+
const second = mapLeaves(node.second, fn);
|
|
70
|
+
if (first && second)
|
|
71
|
+
return { ...node, first, second };
|
|
72
|
+
return first ?? second;
|
|
73
|
+
}
|
|
74
|
+
// Replace one leaf identified by id. Used by split-on-drop.
|
|
75
|
+
function replaceLeaf(node, leafId, replacement) {
|
|
76
|
+
if (node.kind === 'leaf')
|
|
77
|
+
return node.id === leafId ? replacement : node;
|
|
78
|
+
return {
|
|
79
|
+
...node,
|
|
80
|
+
first: replaceLeaf(node.first, leafId, replacement),
|
|
81
|
+
second: replaceLeaf(node.second, leafId, replacement),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// Find a parent split that contains `leafId` directly, return path so we
|
|
85
|
+
// can rewrite ratio at the divider. (Not used for v1; ratio updates flow
|
|
86
|
+
// through replaceSplit below.)
|
|
87
|
+
function replaceSplit(node, match, patch) {
|
|
88
|
+
if (node.kind === 'leaf')
|
|
89
|
+
return node;
|
|
90
|
+
if (match(node))
|
|
91
|
+
return { ...node, ...patch };
|
|
92
|
+
return {
|
|
93
|
+
...node,
|
|
94
|
+
first: replaceSplit(node.first, match, patch),
|
|
95
|
+
second: replaceSplit(node.second, match, patch),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Reassigns ids for any leaf whose id was already seen in this walk. Heals
|
|
99
|
+
// trees persisted before the `newLeafId` collision fix landed — without this,
|
|
100
|
+
// `replaceLeaf` would mutate every leaf sharing the duplicate id at once.
|
|
101
|
+
function dedupeLeafIds(node, seen) {
|
|
102
|
+
if (node.kind === 'leaf') {
|
|
103
|
+
if (seen.has(node.id)) {
|
|
104
|
+
const fresh = newLeafId();
|
|
105
|
+
seen.add(fresh);
|
|
106
|
+
return { ...node, id: fresh };
|
|
107
|
+
}
|
|
108
|
+
seen.add(node.id);
|
|
109
|
+
return node;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
...node,
|
|
113
|
+
first: dedupeLeafIds(node.first, seen),
|
|
114
|
+
second: dedupeLeafIds(node.second, seen),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function reconcile(raw, mountKeys, focusedKey) {
|
|
118
|
+
const alive = new Set(mountKeys);
|
|
119
|
+
let tree = raw
|
|
120
|
+
? dedupeLeafIds(pruneTree(raw, alive), new Set())
|
|
121
|
+
: makeLeaf([...mountKeys], focusedKey);
|
|
122
|
+
// Append orphans (mounts not in tree yet). Prefer the leaf holding the
|
|
123
|
+
// focused key; otherwise the first non-empty leaf; otherwise the first
|
|
124
|
+
// leaf. Intentional empty groups (split-right / split-down) are NOT
|
|
125
|
+
// auto-filled — they wait for an explicit drop.
|
|
126
|
+
const present = new Set();
|
|
127
|
+
collectTabs(tree, present);
|
|
128
|
+
const orphans = mountKeys.filter((k) => !present.has(k));
|
|
129
|
+
if (orphans.length > 0) {
|
|
130
|
+
const leaves = [];
|
|
131
|
+
collectLeaves(tree, leaves);
|
|
132
|
+
const target = (focusedKey && leaves.find((l) => l.tabs.includes(focusedKey))) ||
|
|
133
|
+
leaves.find((l) => l.tabs.length > 0) ||
|
|
134
|
+
leaves[0];
|
|
135
|
+
if (target) {
|
|
136
|
+
// Pick the active tab for the target leaf. Prefer focusedKey when it's
|
|
137
|
+
// one of the orphans we just appended; otherwise the last orphan wins
|
|
138
|
+
// (most recently opened). Without this, the leaf would keep showing
|
|
139
|
+
// whatever was active before — the user clicks "open" and nothing
|
|
140
|
+
// visibly changes.
|
|
141
|
+
const newActive = focusedKey && orphans.includes(focusedKey)
|
|
142
|
+
? focusedKey
|
|
143
|
+
: orphans[orphans.length - 1];
|
|
144
|
+
tree = replaceLeaf(tree, target.id, {
|
|
145
|
+
...target,
|
|
146
|
+
tabs: [...target.tabs, ...orphans],
|
|
147
|
+
activeKey: newActive,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Re-open case: tab already exists in some leaf but focusedKey just
|
|
152
|
+
// changed (surfaces.open → focus emits scena:mount:focused →
|
|
153
|
+
// activeContainerKey updates). Sync the containing leaf's activeKey so the
|
|
154
|
+
// tab actually becomes active in its leaf.
|
|
155
|
+
if (focusedKey) {
|
|
156
|
+
const next = mapLeaves(tree, (l) => {
|
|
157
|
+
if (l.tabs.includes(focusedKey) && l.activeKey !== focusedKey) {
|
|
158
|
+
return { ...l, activeKey: focusedKey };
|
|
159
|
+
}
|
|
160
|
+
return l;
|
|
161
|
+
});
|
|
162
|
+
if (next)
|
|
163
|
+
tree = next;
|
|
164
|
+
}
|
|
165
|
+
return { tree };
|
|
166
|
+
}
|
|
167
|
+
// Structural (key-order-independent) equality for two trees. Used to decide
|
|
168
|
+
// whether the reconciled tree needs to be written back to state — a JSON
|
|
169
|
+
// compare would risk a false-negative on key order and loop the commit effect.
|
|
170
|
+
function treesEqual(a, b) {
|
|
171
|
+
if (a === b)
|
|
172
|
+
return true;
|
|
173
|
+
if (!a || !b || a.kind !== b.kind)
|
|
174
|
+
return false;
|
|
175
|
+
if (a.kind === 'leaf' && b.kind === 'leaf') {
|
|
176
|
+
return (a.id === b.id &&
|
|
177
|
+
a.activeKey === b.activeKey &&
|
|
178
|
+
a.tabs.length === b.tabs.length &&
|
|
179
|
+
a.tabs.every((t, i) => t === b.tabs[i]));
|
|
180
|
+
}
|
|
181
|
+
if (a.kind === 'split' && b.kind === 'split') {
|
|
182
|
+
return (a.direction === b.direction &&
|
|
183
|
+
(a.ratio ?? 0.5) === (b.ratio ?? 0.5) &&
|
|
184
|
+
treesEqual(a.first, b.first) &&
|
|
185
|
+
treesEqual(a.second, b.second));
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
// Renders one leaf: tab strip + group menu + active mount + drop overlay.
|
|
190
|
+
function GroupPanel({ leaf, ctx }) {
|
|
191
|
+
const scena = useScena();
|
|
192
|
+
const activeKey = leaf.activeKey ?? leaf.tabs[0];
|
|
193
|
+
// const activeMount = ctx.mounts.find((m) => m.key === activeKey);
|
|
194
|
+
const [dragOverTab, setDragOverTab] = useState(null);
|
|
195
|
+
const [zone, setZone] = useState(null);
|
|
196
|
+
const bodyRef = useRef(null);
|
|
197
|
+
const tabsRef = useRef(null);
|
|
198
|
+
// Vertical wheel → horizontal scroll on the leaf's tab strip. Native
|
|
199
|
+
// listener (not React's onWheel) so we can preventDefault — React's
|
|
200
|
+
// synthetic wheel is passive and would scroll the surrounding page
|
|
201
|
+
// vertically instead. Same pattern as packages/web's TabBar.
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
const el = tabsRef.current;
|
|
204
|
+
if (!el)
|
|
205
|
+
return;
|
|
206
|
+
function onWheel(e) {
|
|
207
|
+
if (e.deltaY === 0)
|
|
208
|
+
return;
|
|
209
|
+
e.preventDefault();
|
|
210
|
+
el.scrollLeft += e.deltaY;
|
|
211
|
+
}
|
|
212
|
+
el.addEventListener('wheel', onWheel, { passive: false });
|
|
213
|
+
return () => el.removeEventListener('wheel', onWheel);
|
|
214
|
+
}, []);
|
|
215
|
+
// Drop-preview cleanup. Two HTML5 DnD quirks bite here:
|
|
216
|
+
// 1) `dragleave` fires when the cursor enters a CHILD of the body
|
|
217
|
+
// (the child becomes the new target), but we only want to clear when
|
|
218
|
+
// the cursor actually leaves the body's bounds.
|
|
219
|
+
// 2) When the user releases the mouse OR the drag ends on a different
|
|
220
|
+
// element entirely, no `dragleave` may fire on this body at all —
|
|
221
|
+
// so its `zone` state would persist across drags.
|
|
222
|
+
// The window listeners below guarantee zone clears on every drag end/drop
|
|
223
|
+
// regardless of where it happened.
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
const clearAll = () => {
|
|
226
|
+
setZone(null);
|
|
227
|
+
setDragOverTab(null);
|
|
228
|
+
};
|
|
229
|
+
window.addEventListener('dragend', clearAll);
|
|
230
|
+
window.addEventListener('drop', clearAll);
|
|
231
|
+
return () => {
|
|
232
|
+
window.removeEventListener('dragend', clearAll);
|
|
233
|
+
window.removeEventListener('drop', clearAll);
|
|
234
|
+
};
|
|
235
|
+
}, []);
|
|
236
|
+
function setLeafActive(key) {
|
|
237
|
+
const next = mapLeaves(ctx.tree, (l) => l.id === leaf.id ? { ...l, activeKey: key } : l);
|
|
238
|
+
if (next)
|
|
239
|
+
ctx.commit(next);
|
|
240
|
+
ctx.onActivate(key);
|
|
241
|
+
}
|
|
242
|
+
// ----- Tab DnD source -----
|
|
243
|
+
function onTabDragStart(e, tabKey) {
|
|
244
|
+
const payload = { sourceLeafId: leaf.id, tabKey };
|
|
245
|
+
e.dataTransfer.setData(DRAG_MIME, JSON.stringify(payload));
|
|
246
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
247
|
+
}
|
|
248
|
+
// ----- Tab-strip drop (reorder within strip OR move into this leaf) -----
|
|
249
|
+
function onTabStripDragOver(e, idx) {
|
|
250
|
+
if (!e.dataTransfer.types.includes(DRAG_MIME))
|
|
251
|
+
return;
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
e.dataTransfer.dropEffect = 'move';
|
|
254
|
+
setDragOverTab(idx);
|
|
255
|
+
}
|
|
256
|
+
function onTabStripDragLeave() {
|
|
257
|
+
setDragOverTab(null);
|
|
258
|
+
}
|
|
259
|
+
function onTabStripDrop(e, toIdx) {
|
|
260
|
+
if (!e.dataTransfer.types.includes(DRAG_MIME))
|
|
261
|
+
return;
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
setDragOverTab(null);
|
|
264
|
+
const payload = readPayload(e);
|
|
265
|
+
if (!payload)
|
|
266
|
+
return;
|
|
267
|
+
insertTab(ctx, payload, leaf.id, toIdx);
|
|
268
|
+
}
|
|
269
|
+
// ----- Body 5-zone drop -----
|
|
270
|
+
function computeZone(e) {
|
|
271
|
+
const el = bodyRef.current;
|
|
272
|
+
if (!el)
|
|
273
|
+
return null;
|
|
274
|
+
const rect = el.getBoundingClientRect();
|
|
275
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
276
|
+
return null;
|
|
277
|
+
const x = (e.clientX - rect.left) / rect.width;
|
|
278
|
+
const y = (e.clientY - rect.top) / rect.height;
|
|
279
|
+
if (y < EDGE_THRESHOLD)
|
|
280
|
+
return 'top';
|
|
281
|
+
if (y > 1 - EDGE_THRESHOLD)
|
|
282
|
+
return 'bottom';
|
|
283
|
+
if (x < EDGE_THRESHOLD)
|
|
284
|
+
return 'left';
|
|
285
|
+
if (x > 1 - EDGE_THRESHOLD)
|
|
286
|
+
return 'right';
|
|
287
|
+
return 'center';
|
|
288
|
+
}
|
|
289
|
+
function onBodyDragOver(e) {
|
|
290
|
+
if (!e.dataTransfer.types.includes(DRAG_MIME))
|
|
291
|
+
return;
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
e.dataTransfer.dropEffect = 'move';
|
|
294
|
+
setZone(computeZone(e));
|
|
295
|
+
}
|
|
296
|
+
function onBodyDragLeave(e) {
|
|
297
|
+
// Clear only when the cursor leaves the body entirely. `relatedTarget`
|
|
298
|
+
// is the element being entered (if any) — if it's inside the body we're
|
|
299
|
+
// still inside; only clear when it's outside or null (left the window).
|
|
300
|
+
const next = e.relatedTarget;
|
|
301
|
+
if (!next || !e.currentTarget.contains(next)) {
|
|
302
|
+
setZone(null);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function onBodyDrop(e) {
|
|
306
|
+
if (!e.dataTransfer.types.includes(DRAG_MIME))
|
|
307
|
+
return;
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
const z = computeZone(e);
|
|
310
|
+
setZone(null);
|
|
311
|
+
const payload = readPayload(e);
|
|
312
|
+
if (!payload || !z)
|
|
313
|
+
return;
|
|
314
|
+
if (z === 'center') {
|
|
315
|
+
insertTab(ctx, payload, leaf.id, leaf.tabs.length);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
splitLeafOnDrop(ctx, payload, leaf.id, z);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// ----- [...] group menu -----
|
|
322
|
+
// Items come from the `tab-group:context` slot. Layout-commands.ts (group.*)
|
|
323
|
+
// owns the actions; this menu just opens the picker with the right
|
|
324
|
+
// capability paths injected so the commands target this leaf.
|
|
325
|
+
const [menu, setMenu] = useState(null);
|
|
326
|
+
function openGroupMenu(e) {
|
|
327
|
+
e.stopPropagation();
|
|
328
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
329
|
+
setMenu({ x: rect.right - 4, y: rect.bottom + 2 });
|
|
330
|
+
}
|
|
331
|
+
function closeMenu() {
|
|
332
|
+
setMenu(null);
|
|
333
|
+
}
|
|
334
|
+
// ----- Per-tab context menu (right-click on tab) -----
|
|
335
|
+
const [tabMenu, setTabMenu] = useState(null);
|
|
336
|
+
function openTabMenu(e, tabKey, tabIdx) {
|
|
337
|
+
e.preventDefault();
|
|
338
|
+
setTabMenu({ x: e.clientX, y: e.clientY, tabKey, tabIdx });
|
|
339
|
+
}
|
|
340
|
+
function closeTabMenu() {
|
|
341
|
+
setTabMenu(null);
|
|
342
|
+
}
|
|
343
|
+
// Host-contributed rows for the right-clicked tab (e.g. explorer's "Open
|
|
344
|
+
// with <viewer>" alternates). Empty for tabs no host claims.
|
|
345
|
+
const tabMenuMount = tabMenu
|
|
346
|
+
? ctx.mounts.find((m) => m.key === tabMenu.tabKey)
|
|
347
|
+
: undefined;
|
|
348
|
+
const tabMenuExtra = tabMenuMount
|
|
349
|
+
? scena.mountMenus.collect('tab:context', tabMenuMount)
|
|
350
|
+
: undefined;
|
|
351
|
+
return (_jsxs("div", { className: "oo-tab-panel-group", "data-leaf-id": leaf.id, style: {
|
|
352
|
+
flex: 1,
|
|
353
|
+
minWidth: 0,
|
|
354
|
+
minHeight: 0,
|
|
355
|
+
display: 'flex',
|
|
356
|
+
flexDirection: 'column',
|
|
357
|
+
overflow: 'hidden',
|
|
358
|
+
}, children: [_jsxs("div", { className: "tab-bar", children: [_jsx("div", { className: "tab-bar__strip", ref: tabsRef, children: leaf.tabs.map((key, ti) => {
|
|
359
|
+
const m = ctx.mounts.find((mm) => mm.key === key);
|
|
360
|
+
return _jsx(TabTitle, { resourceKey: key, idx: ti, m: m, isActive: key === activeKey, isPinned: ctx.pinned.has(key), dragOverTab: dragOverTab, onDragStart: (e) => onTabDragStart(e, key), onDragOver: (e) => onTabStripDragOver(e, ti), onDragLeave: onTabStripDragLeave, onDrop: (e) => onTabStripDrop(e, ti), onClick: () => setLeafActive(key), onContextMenu: (e) => openTabMenu(e, key, ti), onClose: () => ctx.onClose(key), onUnpin: (e) => {
|
|
361
|
+
e.stopPropagation();
|
|
362
|
+
scena.store.set('$/tab/key', key);
|
|
363
|
+
scena.store.set('$/surface/name', ctx.surface);
|
|
364
|
+
void scena.commands.execute('tab.unpin');
|
|
365
|
+
} }, key);
|
|
366
|
+
}) }), _jsx("div", { className: "tab-bar__extras", children: _jsx("button", { className: "tab-bar__more", title: "Group actions", "aria-label": "Group actions", onClick: openGroupMenu, children: NAMED_GLYPH.moreHoriz }) })] }), _jsxs("div", { ref: bodyRef, onDragOver: onBodyDragOver, onDragLeave: onBodyDragLeave, onDrop: onBodyDrop, style: {
|
|
367
|
+
flex: 1,
|
|
368
|
+
minHeight: 0,
|
|
369
|
+
position: 'relative',
|
|
370
|
+
overflow: 'auto',
|
|
371
|
+
}, children: [leaf.tabs.length === 0 ? (_jsx("div", { style: { padding: 24, color: 'var(--oo-color-muted)' }, children: "Empty group. Drag a tab here." })) : (
|
|
372
|
+
// Keep every tab in the group mounted; show only the active one.
|
|
373
|
+
// Rendering just the active mount unmounts the rest, losing state.
|
|
374
|
+
leaf.tabs.map((key) => {
|
|
375
|
+
const m = ctx.mounts.find((mm) => mm.key === key);
|
|
376
|
+
if (!m)
|
|
377
|
+
return null;
|
|
378
|
+
return (_jsx("div", { className: "oo-layout__mount", "data-active": key === activeKey || undefined, style: { position: 'absolute', inset: 0, overflow: 'auto', display: key === activeKey ? 'block' : 'none' }, children: ctx.renderMount(m) }, key));
|
|
379
|
+
})), zone ? _jsx(DropPreview, { zone: zone }) : null] }), menu ? (_jsx(ContextMenu, { x: menu.x, y: menu.y, onClose: closeMenu, spec: { query: { slot: 'tab-group:context' }, footerHints: true }, context: {
|
|
380
|
+
'$/surface/name': ctx.surface,
|
|
381
|
+
'$/surface/layoutId': 'tab-panel',
|
|
382
|
+
'$/surface/mountCount': ctx.mounts.length,
|
|
383
|
+
'$/surface/closedHistory': 0,
|
|
384
|
+
'$/group/key': leaf.id,
|
|
385
|
+
'$/group/tabCount': leaf.tabs.length,
|
|
386
|
+
'$/group/canClose': true,
|
|
387
|
+
'$/group/canMaximize': false,
|
|
388
|
+
'$/tab/canSplit': true,
|
|
389
|
+
'$/tab/canMove': ctx.leafCount > 1,
|
|
390
|
+
} })) : null, tabMenu ? (_jsx(ContextMenu, { x: tabMenu.x, y: tabMenu.y, onClose: closeTabMenu, spec: { query: { slot: 'tab:context' }, extraItems: tabMenuExtra, footerHints: true }, context: {
|
|
391
|
+
'$/surface/name': ctx.surface,
|
|
392
|
+
'$/surface/layoutId': 'tab-panel',
|
|
393
|
+
'$/surface/mountCount': ctx.mounts.length,
|
|
394
|
+
'$/surface/closedHistory': 0,
|
|
395
|
+
'$/group/key': leaf.id,
|
|
396
|
+
'$/group/tabCount': leaf.tabs.length,
|
|
397
|
+
'$/group/canClose': true,
|
|
398
|
+
'$/tab/key': tabMenu.tabKey,
|
|
399
|
+
'$/tab/index': tabMenu.tabIdx,
|
|
400
|
+
'$/tab/leafId': leaf.id,
|
|
401
|
+
'$/tab/isActive': tabMenu.tabKey === activeKey,
|
|
402
|
+
'$/tab/isPinned': ctx.pinned.has(tabMenu.tabKey),
|
|
403
|
+
'$/tab/canSplit': true,
|
|
404
|
+
'$/tab/canMove': ctx.leafCount > 1,
|
|
405
|
+
} })) : null] }));
|
|
406
|
+
}
|
|
407
|
+
function DropPreview({ zone }) {
|
|
408
|
+
const base = {
|
|
409
|
+
position: 'absolute',
|
|
410
|
+
pointerEvents: 'none',
|
|
411
|
+
background: 'rgba(59,130,246,0.12)',
|
|
412
|
+
border: '2px solid rgba(59,130,246,0.6)',
|
|
413
|
+
boxSizing: 'border-box',
|
|
414
|
+
zIndex: 5,
|
|
415
|
+
transition: 'all 80ms ease-out',
|
|
416
|
+
};
|
|
417
|
+
const style = (() => {
|
|
418
|
+
switch (zone) {
|
|
419
|
+
case 'center':
|
|
420
|
+
return { ...base, inset: 0 };
|
|
421
|
+
case 'top':
|
|
422
|
+
return { ...base, top: 0, left: 0, right: 0, height: '50%' };
|
|
423
|
+
case 'bottom':
|
|
424
|
+
return { ...base, bottom: 0, left: 0, right: 0, height: '50%' };
|
|
425
|
+
case 'left':
|
|
426
|
+
return { ...base, top: 0, bottom: 0, left: 0, width: '50%' };
|
|
427
|
+
case 'right':
|
|
428
|
+
return { ...base, top: 0, bottom: 0, right: 0, width: '50%' };
|
|
429
|
+
}
|
|
430
|
+
})();
|
|
431
|
+
return _jsx("div", { className: "oo-tab-panel-drop-preview", style: style });
|
|
432
|
+
}
|
|
433
|
+
function readPayload(e) {
|
|
434
|
+
const raw = e.dataTransfer.getData(DRAG_MIME);
|
|
435
|
+
if (!raw)
|
|
436
|
+
return null;
|
|
437
|
+
try {
|
|
438
|
+
const obj = JSON.parse(raw);
|
|
439
|
+
if (typeof obj.sourceLeafId !== 'string' || typeof obj.tabKey !== 'string') {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
return obj;
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// Remove `tabKey` from the leaf that holds it; if that leaf is the
|
|
449
|
+
// target's same leaf, just rewrite tabs; otherwise prune empty leaves.
|
|
450
|
+
function removeTab(tree, tabKey) {
|
|
451
|
+
return mapLeaves(tree, (l) => {
|
|
452
|
+
if (!l.tabs.includes(tabKey))
|
|
453
|
+
return l;
|
|
454
|
+
const tabs = l.tabs.filter((k) => k !== tabKey);
|
|
455
|
+
if (tabs.length === 0)
|
|
456
|
+
return null;
|
|
457
|
+
return {
|
|
458
|
+
...l,
|
|
459
|
+
tabs,
|
|
460
|
+
activeKey: l.activeKey === tabKey ? tabs[0] : l.activeKey,
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
function insertTab(ctx, payload, targetLeafId, insertIdx) {
|
|
465
|
+
// Same-leaf reorder
|
|
466
|
+
if (payload.sourceLeafId === targetLeafId) {
|
|
467
|
+
const next = mapLeaves(ctx.tree, (l) => {
|
|
468
|
+
if (l.id !== targetLeafId)
|
|
469
|
+
return l;
|
|
470
|
+
const fromIdx = l.tabs.indexOf(payload.tabKey);
|
|
471
|
+
if (fromIdx === -1)
|
|
472
|
+
return l;
|
|
473
|
+
const tabs = [...l.tabs];
|
|
474
|
+
tabs.splice(fromIdx, 1);
|
|
475
|
+
const adjusted = fromIdx < insertIdx ? insertIdx - 1 : insertIdx;
|
|
476
|
+
tabs.splice(adjusted, 0, payload.tabKey);
|
|
477
|
+
return { ...l, tabs, activeKey: payload.tabKey };
|
|
478
|
+
});
|
|
479
|
+
if (next)
|
|
480
|
+
ctx.commit(next);
|
|
481
|
+
ctx.onActivate(payload.tabKey);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
// Cross-leaf: remove from source, insert into target.
|
|
485
|
+
const removed = removeTab(ctx.tree, payload.tabKey);
|
|
486
|
+
if (!removed)
|
|
487
|
+
return;
|
|
488
|
+
const inserted = mapLeaves(removed, (l) => {
|
|
489
|
+
if (l.id !== targetLeafId)
|
|
490
|
+
return l;
|
|
491
|
+
const tabs = [...l.tabs];
|
|
492
|
+
const clamped = Math.max(0, Math.min(insertIdx, tabs.length));
|
|
493
|
+
tabs.splice(clamped, 0, payload.tabKey);
|
|
494
|
+
return { ...l, tabs, activeKey: payload.tabKey };
|
|
495
|
+
});
|
|
496
|
+
if (inserted)
|
|
497
|
+
ctx.commit(inserted);
|
|
498
|
+
ctx.onActivate(payload.tabKey);
|
|
499
|
+
}
|
|
500
|
+
function splitLeafOnDrop(ctx, payload, targetLeafId, zone) {
|
|
501
|
+
// Edge case: dragging the only tab of a leaf onto itself — no-op.
|
|
502
|
+
let sourceTree = ctx.tree;
|
|
503
|
+
const sourceLeaf = collectLeavesById(sourceTree, payload.sourceLeafId);
|
|
504
|
+
if (sourceLeaf &&
|
|
505
|
+
sourceLeaf.id === targetLeafId &&
|
|
506
|
+
sourceLeaf.tabs.length === 1) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
sourceTree = removeTab(sourceTree, payload.tabKey);
|
|
510
|
+
if (!sourceTree)
|
|
511
|
+
return;
|
|
512
|
+
const target = collectLeavesById(sourceTree, targetLeafId);
|
|
513
|
+
if (!target)
|
|
514
|
+
return;
|
|
515
|
+
const newLeaf = makeLeaf([payload.tabKey], payload.tabKey);
|
|
516
|
+
const direction = zone === 'left' || zone === 'right' ? 'row' : 'column';
|
|
517
|
+
const newFirst = zone === 'left' || zone === 'top' ? newLeaf : target;
|
|
518
|
+
const newSecond = zone === 'left' || zone === 'top' ? target : newLeaf;
|
|
519
|
+
const split = {
|
|
520
|
+
kind: 'split',
|
|
521
|
+
direction,
|
|
522
|
+
ratio: 0.5,
|
|
523
|
+
first: newFirst,
|
|
524
|
+
second: newSecond,
|
|
525
|
+
};
|
|
526
|
+
const next = replaceLeaf(sourceTree, targetLeafId, split);
|
|
527
|
+
ctx.commit(next);
|
|
528
|
+
ctx.onActivate(payload.tabKey);
|
|
529
|
+
}
|
|
530
|
+
function collectLeavesById(node, id) {
|
|
531
|
+
if (node.kind === 'leaf')
|
|
532
|
+
return node.id === id ? node : null;
|
|
533
|
+
return (collectLeavesById(node.first, id) || collectLeavesById(node.second, id));
|
|
534
|
+
}
|
|
535
|
+
// Recursive node renderer with shared container ref for divider math.
|
|
536
|
+
function NodeRender({ node, ctx, }) {
|
|
537
|
+
if (node.kind === 'leaf') {
|
|
538
|
+
return _jsx(GroupPanel, { leaf: node, ctx: ctx });
|
|
539
|
+
}
|
|
540
|
+
return _jsx(SplitRender, { node: node, ctx: ctx });
|
|
541
|
+
}
|
|
542
|
+
function SplitRender({ node, ctx, }) {
|
|
543
|
+
const containerRef = useRef(null);
|
|
544
|
+
const ratio = node.ratio ?? 0.5;
|
|
545
|
+
function onDividerCommit(newRatio) {
|
|
546
|
+
const next = replaceSplit(ctx.tree, (s) => s === node, { ratio: newRatio });
|
|
547
|
+
ctx.commit(next);
|
|
548
|
+
}
|
|
549
|
+
const horizontal = node.direction === 'row';
|
|
550
|
+
return (_jsxs("div", { ref: containerRef, className: "oo-tab-panel-split", "data-direction": node.direction, style: {
|
|
551
|
+
display: 'flex',
|
|
552
|
+
flexDirection: horizontal ? 'row' : 'column',
|
|
553
|
+
flex: 1,
|
|
554
|
+
minWidth: 0,
|
|
555
|
+
minHeight: 0,
|
|
556
|
+
}, children: [_jsx("div", { style: { flex: ratio, minWidth: 0, minHeight: 0, display: 'flex' }, children: _jsx(NodeRender, { node: node.first, ctx: ctx }) }), _jsx(Divisor, { direction: node.direction === 'row' ? 'row' : 'col', containerRef: containerRef, min: MIN_RATIO, max: 1 - MIN_RATIO, onCommit: onDividerCommit }), _jsx("div", { style: { flex: 1 - ratio, minWidth: 0, minHeight: 0, display: 'flex' }, children: _jsx(NodeRender, { node: node.second, ctx: ctx }) })] }));
|
|
557
|
+
}
|
|
558
|
+
// ----- Public component -----
|
|
559
|
+
export function TabPanelLayout({ surface, mounts, state, setState, renderMount, onActivate, onClose, }) {
|
|
560
|
+
const mountKeys = mounts.map((m) => m.key);
|
|
561
|
+
const { tree } = reconcile(state.tabPanel?.tree, mountKeys, state.activeContainerKey);
|
|
562
|
+
// Persist the reconciled tree so `state.tabPanel.tree` always equals what's
|
|
563
|
+
// rendered. reconcile() appends just-opened mounts ("orphans") and prunes
|
|
564
|
+
// closed ones at render time only; without writing that back, the raw
|
|
565
|
+
// persisted tree drifts from the view. Every tab.*/group.* context command
|
|
566
|
+
// resolves its target tabs from the raw tree (layout-commands.ts), so on a
|
|
567
|
+
// drifted tree "Close others" / "Close to the right" act on a stale, partial
|
|
568
|
+
// set — e.g. open three tabs, right-click the middle, Close others, and the
|
|
569
|
+
// just-opened tabs survive. reconcile is idempotent, so this converges in one
|
|
570
|
+
// extra render (treesEqual stops the commit once raw == rendered).
|
|
571
|
+
useEffect(() => {
|
|
572
|
+
if (!treesEqual(state.tabPanel?.tree, tree)) {
|
|
573
|
+
setState({ tabPanel: { tree } });
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
// Total leaf count drives the `$/tab/canMove` capability — `move to group`
|
|
577
|
+
// only makes sense when there's somewhere to move to.
|
|
578
|
+
const leaves = [];
|
|
579
|
+
collectLeaves(tree, leaves);
|
|
580
|
+
const ctx = {
|
|
581
|
+
surface,
|
|
582
|
+
mounts,
|
|
583
|
+
renderMount,
|
|
584
|
+
onActivate,
|
|
585
|
+
onClose,
|
|
586
|
+
tree,
|
|
587
|
+
commit: (next) => setState({ tabPanel: { tree: next } }),
|
|
588
|
+
leafCount: leaves.length,
|
|
589
|
+
pinned: new Set(state.split?.pinned ?? []),
|
|
590
|
+
};
|
|
591
|
+
return (_jsx("div", { className: "oo-layout oo-layout--tab-panel", style: {
|
|
592
|
+
display: 'flex',
|
|
593
|
+
height: '100%',
|
|
594
|
+
minHeight: 0,
|
|
595
|
+
}, children: _jsx(NodeRender, { node: tree, ctx: ctx }) }));
|
|
596
|
+
}
|
|
597
|
+
export function TabTitle({ m, resourceKey, isActive, dragOverTab, idx, onDragStart, onDragOver, onDragLeave, onDrop, onClick, onContextMenu, onClose, onUnpin, isPinned }) {
|
|
598
|
+
const scena = useScena();
|
|
599
|
+
// Per-field merge: mount.props ?? component.props ?? key.
|
|
600
|
+
const compProps = m
|
|
601
|
+
? scena.components.get(m.component.component)?.props
|
|
602
|
+
: undefined;
|
|
603
|
+
const title = useStoreLabel(m?.props?.title ?? compProps?.title, '', m?.dataContext) || resourceKey;
|
|
604
|
+
const icon = m?.props?.icon ?? compProps?.icon;
|
|
605
|
+
const color = m?.props?.color ?? compProps?.color;
|
|
606
|
+
return (_jsxs("div", { className: "tab-bar__tab", "data-active": isActive, "data-drag-over": dragOverTab === idx ? 'true' : undefined, "data-color": color, "data-pinned": isPinned || undefined, draggable: true, onDragStart: onDragStart, onDragOver: onDragOver, onDragLeave: onDragLeave, onDrop: onDrop, onClick: onClick, onContextMenu: onContextMenu, title: title, style: {
|
|
607
|
+
...(color ? { '--tab-color': resolveColorRgb(color) } : {})
|
|
608
|
+
}, children: [icon ? _jsx("span", { className: "tab__icon", "aria-hidden": true, children: icon }) : null, _jsx("span", { className: "tab__label", children: title }), isPinned ? (_jsx("span", { className: "pin", onClick: (e) => {
|
|
609
|
+
e.stopPropagation();
|
|
610
|
+
onUnpin(e);
|
|
611
|
+
}, title: "Unpin", children: NAMED_GLYPH.pin })) : (_jsx("span", { className: "close", onClick: (e) => {
|
|
612
|
+
e.stopPropagation();
|
|
613
|
+
onClose(e);
|
|
614
|
+
}, title: "Close", children: NAMED_GLYPH.closePanel }))] }));
|
|
615
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Catalog registration for layout primitives. Dynamic import() so importing
|
|
2
|
+
// this file pulls nothing eager — components load on first render. Direct
|
|
3
|
+
// re-exports for React use live in ./index.ts; the lazy layout strategies are
|
|
4
|
+
// in ./register.ts.
|
|
5
|
+
export const CATALOG_LAYOUT_MAP = [
|
|
6
|
+
{ component: 'Row', category: 'inline', renderer: { kind: 'react', load: () => import('./Row.js').then((m) => ({ default: m.Row })) } },
|
|
7
|
+
{ component: 'Column', category: 'inline', renderer: { kind: 'react', load: () => import('./Column.js').then((m) => ({ default: m.Column })) } },
|
|
8
|
+
{ component: 'Grid', category: 'inline', renderer: { kind: 'react', load: () => import('./Grid.js').then((m) => ({ default: m.Grid })) } },
|
|
9
|
+
{ component: 'Splitter', category: 'page', renderer: { kind: 'react', load: () => import('./Splitter.js').then((m) => ({ default: m.Splitter })) } },
|
|
10
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { Row } from './Row.js';
|
|
2
|
+
export { Column } from './Column.js';
|
|
3
|
+
export { Grid } from './Grid.js';
|
|
4
|
+
export type { GridProps } from './Grid.js';
|
|
5
|
+
export { TabLayout } from './TabLayout.js';
|
|
6
|
+
export { TabPanelLayout } from './TabPanelLayout.js';
|
|
7
|
+
export { SingleLayout } from './SingleLayout.js';
|
|
8
|
+
export { SplitLayout } from './SplitLayout.js';
|
|
9
|
+
export { StackLayout } from './StackLayout.js';
|
|
10
|
+
export { SpatialLayout } from './SpatialLayout.js';
|
|
11
|
+
export { SpatialCard } from './SpatialCard.js';
|
|
12
|
+
export type { SpatialCardData } from './SpatialCard.js';
|
|
13
|
+
export { RailLayout } from './RailLayout.js';
|
|
14
|
+
export { InlineLayout } from './InlineLayout.js';
|
|
15
|
+
export { BarLayout } from './BarLayout.js';
|
|
16
|
+
export { FloatingLayout } from './FloatingLayout.js';
|
|
17
|
+
export { registerBuiltinLayouts } from './register.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Direct-React re-exports. Catalog registration (Row/Column/Grid) lives in
|
|
2
|
+
// ../catalog.ts; the layout strategies are registered (lazily) by ./register.ts.
|
|
3
|
+
export { Row } from './Row.js';
|
|
4
|
+
export { Column } from './Column.js';
|
|
5
|
+
export { Grid } from './Grid.js';
|
|
6
|
+
export { TabLayout } from './TabLayout.js';
|
|
7
|
+
export { TabPanelLayout } from './TabPanelLayout.js';
|
|
8
|
+
export { SingleLayout } from './SingleLayout.js';
|
|
9
|
+
export { SplitLayout } from './SplitLayout.js';
|
|
10
|
+
export { StackLayout } from './StackLayout.js';
|
|
11
|
+
export { SpatialLayout } from './SpatialLayout.js';
|
|
12
|
+
export { SpatialCard } from './SpatialCard.js';
|
|
13
|
+
export { RailLayout } from './RailLayout.js';
|
|
14
|
+
export { InlineLayout } from './InlineLayout.js';
|
|
15
|
+
export { BarLayout } from './BarLayout.js';
|
|
16
|
+
export { FloatingLayout } from './FloatingLayout.js';
|
|
17
|
+
export { registerBuiltinLayouts } from './register.js';
|