@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,548 @@
|
|
|
1
|
+
import { combineDisposables } from '../core/disposable.js';
|
|
2
|
+
import { translate } from '../i18n/registry.js';
|
|
3
|
+
function joinAbs(root, rel) {
|
|
4
|
+
if (rel.startsWith('$/'))
|
|
5
|
+
return rel;
|
|
6
|
+
if (!root)
|
|
7
|
+
return (rel.startsWith('/') ? `$${rel}` : `$/${rel}`);
|
|
8
|
+
const base = root.endsWith('/') ? root.slice(0, -1) : root;
|
|
9
|
+
const tail = rel.startsWith('/') ? rel : `/${rel}`;
|
|
10
|
+
return `${base}${tail}`;
|
|
11
|
+
}
|
|
12
|
+
// Registers the mount-level / group-level / surface-level commands from
|
|
13
|
+
// COMMANDS-list.md. Layouts inject `$/tab/*`, `$/group/*`, `$/surface/*`
|
|
14
|
+
// capability paths when opening their context menus; these commands read
|
|
15
|
+
// from those paths to find their targets. `when` clauses gate visibility
|
|
16
|
+
// via commands.list({enabled: true}) — `when` is NOT checked at execute
|
|
17
|
+
// time (decoupled in controls/command.ts), so the run body must be its
|
|
18
|
+
// own last-mile guard if anything is required.
|
|
19
|
+
export function registerLayoutCommands(scena) {
|
|
20
|
+
const subs = [];
|
|
21
|
+
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
22
|
+
function ctxTab(ctx) {
|
|
23
|
+
return ctx.store.get('$/tab/key');
|
|
24
|
+
}
|
|
25
|
+
function ctxGroup(ctx) {
|
|
26
|
+
return ctx.store.get('$/group/key');
|
|
27
|
+
}
|
|
28
|
+
function ctxSurface(ctx) {
|
|
29
|
+
return (ctx.store.get('$/surface/name') ?? 'main');
|
|
30
|
+
}
|
|
31
|
+
function ctxTabIndex(ctx) {
|
|
32
|
+
return Number(ctx.store.get('$/tab/index') ?? 0);
|
|
33
|
+
}
|
|
34
|
+
// Read the surface's current state from layout. Most commands operate
|
|
35
|
+
// through `surfaces.close` / `surfaces.open`; only group-level tree
|
|
36
|
+
// mutations need the layout tree directly.
|
|
37
|
+
function surfaceState(ctx) {
|
|
38
|
+
const surface = ctxSurface(ctx);
|
|
39
|
+
return ctx.scena.layout.get().surfaces[surface];
|
|
40
|
+
}
|
|
41
|
+
function setTabPanelTree(ctx, tree) {
|
|
42
|
+
const surface = ctxSurface(ctx);
|
|
43
|
+
const cur = ctx.scena.layout.get().surfaces[surface];
|
|
44
|
+
ctx.scena.layout.setSurface(surface, {
|
|
45
|
+
...cur,
|
|
46
|
+
tabPanel: tree ? { tree } : undefined,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Set of pinned mount keys on the right-clicked surface. Bulk-close
|
|
50
|
+
// commands (closeOthers, closeToRight, ...) filter through this so pinned
|
|
51
|
+
// tabs survive "close all but me"-style operations — matches VS Code.
|
|
52
|
+
function pinnedKeys(ctx) {
|
|
53
|
+
const cur = surfaceState(ctx);
|
|
54
|
+
return new Set(cur?.split?.pinned ?? []);
|
|
55
|
+
}
|
|
56
|
+
// Pin / unpin a tab on the right-clicked surface. Pinned mount keys live
|
|
57
|
+
// in `state.split.pinned` so they persist with the layout; layouts swap
|
|
58
|
+
// the × close affordance for a pin icon when a tab is in this set.
|
|
59
|
+
function togglePinned(ctx, key, pin) {
|
|
60
|
+
const surface = ctxSurface(ctx);
|
|
61
|
+
const cur = ctx.scena.layout.get().surfaces[surface];
|
|
62
|
+
const current = new Set(cur?.split?.pinned ?? []);
|
|
63
|
+
if (pin)
|
|
64
|
+
current.add(key);
|
|
65
|
+
else
|
|
66
|
+
current.delete(key);
|
|
67
|
+
ctx.scena.layout.setSurface(surface, {
|
|
68
|
+
...cur,
|
|
69
|
+
split: { ...(cur?.split ?? {}), pinned: [...current] },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Walk the tab-panel tree to find a leaf by id; returns the leaf + the
|
|
73
|
+
// ordered list of tab keys in it (so commands can find siblings).
|
|
74
|
+
function findLeaf(tree, leafId) {
|
|
75
|
+
if (tree.kind === 'leaf')
|
|
76
|
+
return tree.id === leafId ? tree : null;
|
|
77
|
+
return findLeaf(tree.first, leafId) ?? findLeaf(tree.second, leafId);
|
|
78
|
+
}
|
|
79
|
+
// Collect all leaves so commands like `tab.moveToGroup` can list them.
|
|
80
|
+
function collectLeaves(tree, out = []) {
|
|
81
|
+
if (tree.kind === 'leaf') {
|
|
82
|
+
out.push(tree);
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
collectLeaves(tree.first, out);
|
|
86
|
+
collectLeaves(tree.second, out);
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
function replaceLeaf(node, leafId, replacement) {
|
|
90
|
+
if (node.kind === 'leaf')
|
|
91
|
+
return node.id === leafId ? replacement : node;
|
|
92
|
+
return {
|
|
93
|
+
...node,
|
|
94
|
+
first: replaceLeaf(node.first, leafId, replacement),
|
|
95
|
+
second: replaceLeaf(node.second, leafId, replacement),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function mapLeaves(node, fn) {
|
|
99
|
+
if (node.kind === 'leaf')
|
|
100
|
+
return fn(node);
|
|
101
|
+
const first = mapLeaves(node.first, fn);
|
|
102
|
+
const second = mapLeaves(node.second, fn);
|
|
103
|
+
if (first && second)
|
|
104
|
+
return { ...node, first, second };
|
|
105
|
+
return first ?? second;
|
|
106
|
+
}
|
|
107
|
+
function newLeafId() {
|
|
108
|
+
return `g_${Math.random().toString(36).slice(2, 10)}`;
|
|
109
|
+
}
|
|
110
|
+
// ── Mount-level commands ─────────────────────────────────────────────────
|
|
111
|
+
subs.push(scena.commands.register({
|
|
112
|
+
id: 'tab.close',
|
|
113
|
+
title: () => translate('tabs/close', 'Close tab'),
|
|
114
|
+
icon: '✕',
|
|
115
|
+
color: 'red',
|
|
116
|
+
slots: ['tab:context', 'card:context'],
|
|
117
|
+
// Pinned tabs hide the close affordance entirely — the menu shows
|
|
118
|
+
// Unpin instead, and the user must unpin first to close. Matches
|
|
119
|
+
// VS Code-style pin semantics.
|
|
120
|
+
when: '$/tab/isPinned == false',
|
|
121
|
+
run: (ctx) => {
|
|
122
|
+
const key = ctxTab(ctx);
|
|
123
|
+
if (!key)
|
|
124
|
+
return;
|
|
125
|
+
ctx.store.set(joinAbs(ctx.dataContext, '/lastClosed'), String(key));
|
|
126
|
+
ctx.surfaces.close(key);
|
|
127
|
+
ctx.host?.closeMenu();
|
|
128
|
+
},
|
|
129
|
+
}), scena.commands.register({
|
|
130
|
+
id: 'tab.closeOthers',
|
|
131
|
+
// title: 'Close others',
|
|
132
|
+
title: () => translate('tabs/closeOthers', 'Close others'),
|
|
133
|
+
icon: '−',
|
|
134
|
+
color: 'orange',
|
|
135
|
+
slots: ['tab:context'],
|
|
136
|
+
when: '$/group/tabCount > 1',
|
|
137
|
+
run: (ctx) => {
|
|
138
|
+
const key = ctxTab(ctx);
|
|
139
|
+
const leafId = ctx.store.get('$/tab/leafId');
|
|
140
|
+
if (!key)
|
|
141
|
+
return;
|
|
142
|
+
const keys = tabsInLeafOrSurface(ctx, leafId);
|
|
143
|
+
const pinned = pinnedKeys(ctx);
|
|
144
|
+
for (const k of keys)
|
|
145
|
+
if (k !== key && !pinned.has(k))
|
|
146
|
+
ctx.surfaces.close(k);
|
|
147
|
+
ctx.host?.closeMenu();
|
|
148
|
+
},
|
|
149
|
+
}), scena.commands.register({
|
|
150
|
+
id: 'tab.closeToRight',
|
|
151
|
+
title: () => translate('tabs/closeRight', 'Close to the right'),
|
|
152
|
+
icon: '⇥',
|
|
153
|
+
color: 'orange',
|
|
154
|
+
slots: ['tab:context'],
|
|
155
|
+
when: '$/group/tabCount > 1',
|
|
156
|
+
run: (ctx) => {
|
|
157
|
+
const key = ctxTab(ctx);
|
|
158
|
+
if (!key)
|
|
159
|
+
return;
|
|
160
|
+
const leafId = ctx.store.get('$/tab/leafId');
|
|
161
|
+
const keys = tabsInLeafOrSurface(ctx, leafId);
|
|
162
|
+
const idx = ctxTabIndex(ctx);
|
|
163
|
+
const pinned = pinnedKeys(ctx);
|
|
164
|
+
for (let i = idx + 1; i < keys.length; i++) {
|
|
165
|
+
const k = keys[i];
|
|
166
|
+
if (k && !pinned.has(k))
|
|
167
|
+
ctx.surfaces.close(k);
|
|
168
|
+
}
|
|
169
|
+
ctx.host?.closeMenu();
|
|
170
|
+
},
|
|
171
|
+
}), scena.commands.register({
|
|
172
|
+
id: 'tab.closeToLeft',
|
|
173
|
+
title: () => translate('tabs/closeLeft', 'Close to the left'),
|
|
174
|
+
icon: '⇤',
|
|
175
|
+
color: 'orange',
|
|
176
|
+
slots: ['tab:context'],
|
|
177
|
+
when: '$/group/tabCount > 1 && $/tab/index > 0',
|
|
178
|
+
run: (ctx) => {
|
|
179
|
+
const leafId = ctx.store.get('$/tab/leafId');
|
|
180
|
+
const keys = tabsInLeafOrSurface(ctx, leafId);
|
|
181
|
+
const idx = ctxTabIndex(ctx);
|
|
182
|
+
const pinned = pinnedKeys(ctx);
|
|
183
|
+
for (let i = 0; i < idx; i++) {
|
|
184
|
+
const k = keys[i];
|
|
185
|
+
if (k && !pinned.has(k))
|
|
186
|
+
ctx.surfaces.close(k);
|
|
187
|
+
}
|
|
188
|
+
ctx.host?.closeMenu();
|
|
189
|
+
},
|
|
190
|
+
}), scena.commands.register({
|
|
191
|
+
id: 'tab.pin',
|
|
192
|
+
title: () => translate('tabs/pin', 'Pin tab'),
|
|
193
|
+
icon: '📌︎',
|
|
194
|
+
color: 'amber',
|
|
195
|
+
slots: ['tab:context'],
|
|
196
|
+
// Only visible when this tab is NOT already pinned. The previous
|
|
197
|
+
// implementation wrote a global `/pinned = true` boolean via
|
|
198
|
+
// `joinAbs(ctx.dataContext, '/pinned')` — that affected nothing the
|
|
199
|
+
// layouts read. Per-tab pin state belongs in the surface's
|
|
200
|
+
// `state.split.pinned` array so it persists with the layout.
|
|
201
|
+
when: '$/tab/isPinned == false',
|
|
202
|
+
run: (ctx) => {
|
|
203
|
+
const key = ctxTab(ctx);
|
|
204
|
+
if (!key)
|
|
205
|
+
return;
|
|
206
|
+
togglePinned(ctx, key, true);
|
|
207
|
+
ctx.host?.closeMenu();
|
|
208
|
+
},
|
|
209
|
+
}), scena.commands.register({
|
|
210
|
+
id: 'tab.unpin',
|
|
211
|
+
title: () => translate('tabs/unpin', 'Unpin tab'),
|
|
212
|
+
icon: '📌︎',
|
|
213
|
+
color: 'amber',
|
|
214
|
+
slots: ['tab:context'],
|
|
215
|
+
when: '$/tab/isPinned == true',
|
|
216
|
+
run: (ctx) => {
|
|
217
|
+
const key = ctxTab(ctx);
|
|
218
|
+
if (!key)
|
|
219
|
+
return;
|
|
220
|
+
togglePinned(ctx, key, false);
|
|
221
|
+
ctx.host?.closeMenu();
|
|
222
|
+
},
|
|
223
|
+
}), scena.commands.register({
|
|
224
|
+
id: 'tab.copyKey',
|
|
225
|
+
title: () => translate('tabs/copyKey', 'Copy key'),
|
|
226
|
+
icon: '⎘',
|
|
227
|
+
color: 'sky',
|
|
228
|
+
slots: ['tab:context', 'card:context'],
|
|
229
|
+
run: (ctx) => {
|
|
230
|
+
const key = ctxTab(ctx);
|
|
231
|
+
if (!key)
|
|
232
|
+
return;
|
|
233
|
+
void navigator.clipboard?.writeText(key);
|
|
234
|
+
ctx.host?.closeMenu();
|
|
235
|
+
},
|
|
236
|
+
}), scena.commands.register({
|
|
237
|
+
id: 'tab.openToRight',
|
|
238
|
+
title: () => translate('tabs/openToRight', 'Open to the right'),
|
|
239
|
+
icon: '⇨',
|
|
240
|
+
color: 'blue',
|
|
241
|
+
slots: ['tab:context'],
|
|
242
|
+
when: '$/tab/canSplit == true',
|
|
243
|
+
run: (ctx) => splitTabIntoNewLeaf(ctx, 'right'),
|
|
244
|
+
}), scena.commands.register({
|
|
245
|
+
id: 'tab.openToBottom',
|
|
246
|
+
title: () => translate('tabs/openToBottom', 'Open to the bottom'),
|
|
247
|
+
icon: '⇩',
|
|
248
|
+
color: 'blue',
|
|
249
|
+
slots: ['tab:context'],
|
|
250
|
+
when: '$/tab/canSplit == true',
|
|
251
|
+
run: (ctx) => splitTabIntoNewLeaf(ctx, 'bottom'),
|
|
252
|
+
}), scena.commands.register({
|
|
253
|
+
id: 'tab.openToLeft',
|
|
254
|
+
title: () => translate('tabs/openToLeft', 'Open to the left'),
|
|
255
|
+
icon: '⇦',
|
|
256
|
+
color: 'blue',
|
|
257
|
+
slots: ['tab:context'],
|
|
258
|
+
when: '$/tab/canSplit == true',
|
|
259
|
+
run: (ctx) => splitTabIntoNewLeaf(ctx, 'left'),
|
|
260
|
+
}), scena.commands.register({
|
|
261
|
+
id: 'tab.openToTop',
|
|
262
|
+
title: () => translate('tabs/openToTop', 'Open to the top'),
|
|
263
|
+
icon: '⇧',
|
|
264
|
+
color: 'blue',
|
|
265
|
+
slots: ['tab:context'],
|
|
266
|
+
when: '$/tab/canSplit == true',
|
|
267
|
+
run: (ctx) => splitTabIntoNewLeaf(ctx, 'top'),
|
|
268
|
+
}));
|
|
269
|
+
// ── Group-level commands ─────────────────────────────────────────────────
|
|
270
|
+
subs.push(scena.commands.register({
|
|
271
|
+
id: 'group.closeAll',
|
|
272
|
+
title: () => translate('tabs/closeAll', 'Close all in group'),
|
|
273
|
+
icon: '✖',
|
|
274
|
+
color: 'red',
|
|
275
|
+
slots: ['tab:context', 'tab-group:context'],
|
|
276
|
+
when: '$/group/tabCount > 0',
|
|
277
|
+
run: (ctx) => {
|
|
278
|
+
const leafId = ctxGroup(ctx);
|
|
279
|
+
const keys = tabsInLeafOrSurface(ctx, leafId);
|
|
280
|
+
const pinned = pinnedKeys(ctx);
|
|
281
|
+
for (const k of keys)
|
|
282
|
+
if (!pinned.has(k))
|
|
283
|
+
ctx.surfaces.close(k);
|
|
284
|
+
ctx.host?.closeMenu();
|
|
285
|
+
},
|
|
286
|
+
}), scena.commands.register({
|
|
287
|
+
id: 'group.splitRight',
|
|
288
|
+
title: () => translate('tabs/splitRight', 'Split group right'),
|
|
289
|
+
icon: '⇨',
|
|
290
|
+
color: 'blue',
|
|
291
|
+
slots: ['tab-group:context'],
|
|
292
|
+
when: '$/tab/canSplit == true',
|
|
293
|
+
run: (ctx) => splitGroup(ctx, 'row', 'second'),
|
|
294
|
+
}), scena.commands.register({
|
|
295
|
+
id: 'group.splitDown',
|
|
296
|
+
title: () => translate('tabs/splitDown', 'Split group down'),
|
|
297
|
+
icon: '⇩',
|
|
298
|
+
color: 'blue',
|
|
299
|
+
slots: ['tab-group:context'],
|
|
300
|
+
when: '$/tab/canSplit == true',
|
|
301
|
+
run: (ctx) => splitGroup(ctx, 'column', 'second'),
|
|
302
|
+
}), scena.commands.register({
|
|
303
|
+
id: 'group.splitLeft',
|
|
304
|
+
title: () => translate('tabs/splitLeft', 'Split group left'),
|
|
305
|
+
icon: '⇦',
|
|
306
|
+
color: 'blue',
|
|
307
|
+
slots: ['tab-group:context'],
|
|
308
|
+
when: '$/tab/canSplit == true',
|
|
309
|
+
run: (ctx) => splitGroup(ctx, 'row', 'first'),
|
|
310
|
+
}), scena.commands.register({
|
|
311
|
+
id: 'group.splitUp',
|
|
312
|
+
title: () => translate('tabs/splitUp', 'Split group up'),
|
|
313
|
+
icon: '⇧',
|
|
314
|
+
color: 'blue',
|
|
315
|
+
slots: ['tab-group:context'],
|
|
316
|
+
when: '$/tab/canSplit == true',
|
|
317
|
+
run: (ctx) => splitGroup(ctx, 'column', 'first'),
|
|
318
|
+
}),
|
|
319
|
+
// scena.commands.register({
|
|
320
|
+
// id: 'group.closeOthersInGroup',
|
|
321
|
+
// title: () => translate('tabs/closeOthersInGroup', 'Close others (except active)'),
|
|
322
|
+
// icon: '✖',
|
|
323
|
+
// color: 'red',
|
|
324
|
+
// slots: ['tab-group:context'],
|
|
325
|
+
// when: '$/group/tabCount > 1',
|
|
326
|
+
// run: (ctx) => {
|
|
327
|
+
// const leafId = ctxGroup(ctx);
|
|
328
|
+
// const state = surfaceState(ctx);
|
|
329
|
+
// const activeKey = state?.activeContainerKey;
|
|
330
|
+
// const keys = tabsInLeafOrSurface(ctx, leafId);
|
|
331
|
+
// const pinned = pinnedKeys(ctx);
|
|
332
|
+
// for (const k of keys) if (k !== activeKey && !pinned.has(k)) ctx.surfaces.close(k);
|
|
333
|
+
// ctx.host?.closeMenu();
|
|
334
|
+
// },
|
|
335
|
+
// }),
|
|
336
|
+
scena.commands.register({
|
|
337
|
+
id: 'group.closeOtherGroups',
|
|
338
|
+
title: () => translate('tabs/closeOtherGroups', 'Close others (except active group)'),
|
|
339
|
+
icon: '✖',
|
|
340
|
+
color: 'red',
|
|
341
|
+
slots: ['tab-group:context'],
|
|
342
|
+
// when: '$/surface/layoutId == "tab-panel"',
|
|
343
|
+
run: (ctx) => {
|
|
344
|
+
const leafId = ctxGroup(ctx);
|
|
345
|
+
const state = surfaceState(ctx);
|
|
346
|
+
const tree = state?.tabPanel?.tree;
|
|
347
|
+
if (!tree || !leafId)
|
|
348
|
+
return;
|
|
349
|
+
const target = findLeaf(tree, leafId);
|
|
350
|
+
if (!target)
|
|
351
|
+
return;
|
|
352
|
+
// Close every tab not in target leaf, except pinned ones.
|
|
353
|
+
const keep = new Set(target.tabs);
|
|
354
|
+
const pinned = pinnedKeys(ctx);
|
|
355
|
+
const others = [];
|
|
356
|
+
for (const leaf of collectLeaves(tree)) {
|
|
357
|
+
if (leaf.id === leafId)
|
|
358
|
+
continue;
|
|
359
|
+
for (const k of leaf.tabs)
|
|
360
|
+
if (!keep.has(k) && !pinned.has(k))
|
|
361
|
+
others.push(k);
|
|
362
|
+
}
|
|
363
|
+
for (const k of others)
|
|
364
|
+
ctx.surfaces.close(k);
|
|
365
|
+
// Drop all other leaves from the tree. Pinned tabs in those leaves
|
|
366
|
+
// were skipped above and remain in the registry — they'll get
|
|
367
|
+
// re-homed into the surviving target leaf by TabPanelLayout's
|
|
368
|
+
// orphan-reattach logic on the next render.
|
|
369
|
+
const next = mapLeaves(tree, (l) => (l.id === leafId ? l : null));
|
|
370
|
+
setTabPanelTree(ctx, next);
|
|
371
|
+
ctx.host?.closeMenu();
|
|
372
|
+
},
|
|
373
|
+
}), scena.commands.register({
|
|
374
|
+
id: 'group.close',
|
|
375
|
+
title: () => translate('tabs/closeGroup', 'Close group'),
|
|
376
|
+
icon: '✖',
|
|
377
|
+
color: 'red',
|
|
378
|
+
slots: ['tab-group:context'],
|
|
379
|
+
when: '$/group/canClose == true',
|
|
380
|
+
run: (ctx) => {
|
|
381
|
+
const leafId = ctxGroup(ctx);
|
|
382
|
+
const state = surfaceState(ctx);
|
|
383
|
+
const tree = state?.tabPanel?.tree;
|
|
384
|
+
if (!tree || !leafId)
|
|
385
|
+
return;
|
|
386
|
+
const target = findLeaf(tree, leafId);
|
|
387
|
+
if (!target)
|
|
388
|
+
return;
|
|
389
|
+
const pinned = pinnedKeys(ctx);
|
|
390
|
+
for (const k of target.tabs)
|
|
391
|
+
if (!pinned.has(k))
|
|
392
|
+
ctx.surfaces.close(k);
|
|
393
|
+
const next = mapLeaves(tree, (l) => (l.id === leafId ? null : l));
|
|
394
|
+
setTabPanelTree(ctx, next);
|
|
395
|
+
ctx.host?.closeMenu();
|
|
396
|
+
},
|
|
397
|
+
}));
|
|
398
|
+
// ── Surface-level commands ───────────────────────────────────────────────
|
|
399
|
+
subs.push(scena.commands.register({
|
|
400
|
+
id: 'surface.closeAll',
|
|
401
|
+
title: () => translate('tabs/closeAll', 'Close all'),
|
|
402
|
+
icon: '✖',
|
|
403
|
+
color: 'red',
|
|
404
|
+
slots: ['surface:context'],
|
|
405
|
+
when: '$/surface/mountCount > 0',
|
|
406
|
+
run: (ctx) => {
|
|
407
|
+
const surface = ctxSurface(ctx);
|
|
408
|
+
const mounts = ctx.scena.surfaces.listAt(surface);
|
|
409
|
+
const pinned = pinnedKeys(ctx);
|
|
410
|
+
for (const m of mounts)
|
|
411
|
+
if (!pinned.has(m.key))
|
|
412
|
+
ctx.surfaces.close(m.key);
|
|
413
|
+
ctx.host?.closeMenu();
|
|
414
|
+
},
|
|
415
|
+
}), ...layoutSwitchCommands(scena));
|
|
416
|
+
// Prune stale entries from every surface's `state.split.pinned` whenever
|
|
417
|
+
// a mount closes — covers the bulk-close commands above AND any other
|
|
418
|
+
// close path (× button, programmatic close, etc.). Without this, closing
|
|
419
|
+
// a pinned tab leaves its key in the array, so re-opening the same key
|
|
420
|
+
// later would resurrect a phantom pinned state.
|
|
421
|
+
subs.push(scena.events.on('scena:mount:closed', (raw) => {
|
|
422
|
+
const ev = raw;
|
|
423
|
+
const layout = scena.layout.get();
|
|
424
|
+
for (const [surface, st] of Object.entries(layout.surfaces)) {
|
|
425
|
+
const pinned = st?.split?.pinned;
|
|
426
|
+
if (!pinned || !pinned.includes(ev.key))
|
|
427
|
+
continue;
|
|
428
|
+
scena.layout.setSurface(surface, {
|
|
429
|
+
...st,
|
|
430
|
+
split: { ...st.split, pinned: pinned.filter((k) => k !== ev.key) },
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}));
|
|
434
|
+
return combineDisposables(...subs);
|
|
435
|
+
// ── Internal helpers (closures over the helpers above) ───────────────────
|
|
436
|
+
function tabsInLeafOrSurface(ctx, leafId) {
|
|
437
|
+
const surface = ctxSurface(ctx);
|
|
438
|
+
if (leafId) {
|
|
439
|
+
const state = ctx.scena.layout.get().surfaces[surface];
|
|
440
|
+
const tree = state?.tabPanel?.tree;
|
|
441
|
+
if (tree) {
|
|
442
|
+
const leaf = findLeaf(tree, leafId);
|
|
443
|
+
if (leaf)
|
|
444
|
+
return [...leaf.tabs];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return ctx.scena.surfaces.listAt(surface).map((m) => m.key);
|
|
448
|
+
}
|
|
449
|
+
// For TabPanelLayout: split the right-clicked leaf, moving the right-clicked
|
|
450
|
+
// tab into a new sibling leaf on the given side. Wires the new leaf into
|
|
451
|
+
// the tree via replaceLeaf. The drag-drop equivalent of this lives in
|
|
452
|
+
// TabPanelLayout's splitLeafOnDrop — same shape.
|
|
453
|
+
function splitTabIntoNewLeaf(ctx, side) {
|
|
454
|
+
const key = ctxTab(ctx);
|
|
455
|
+
const leafId = ctx.store.get('$/tab/leafId');
|
|
456
|
+
if (!key || !leafId)
|
|
457
|
+
return;
|
|
458
|
+
const state = surfaceState(ctx);
|
|
459
|
+
const tree = state?.tabPanel?.tree;
|
|
460
|
+
if (!tree)
|
|
461
|
+
return;
|
|
462
|
+
const sourceLeaf = findLeaf(tree, leafId);
|
|
463
|
+
if (!sourceLeaf)
|
|
464
|
+
return;
|
|
465
|
+
// Remove the tab from its source leaf.
|
|
466
|
+
const remaining = sourceLeaf.tabs.filter((k) => k !== key);
|
|
467
|
+
const sourceUpdated = {
|
|
468
|
+
...sourceLeaf,
|
|
469
|
+
tabs: remaining,
|
|
470
|
+
activeKey: remaining.includes(sourceLeaf.activeKey ?? '')
|
|
471
|
+
? sourceLeaf.activeKey
|
|
472
|
+
: remaining[0],
|
|
473
|
+
};
|
|
474
|
+
const newLeaf = {
|
|
475
|
+
kind: 'leaf',
|
|
476
|
+
id: newLeafId(),
|
|
477
|
+
tabs: [key],
|
|
478
|
+
activeKey: key,
|
|
479
|
+
};
|
|
480
|
+
const direction = side === 'left' || side === 'right' ? 'row' : 'column';
|
|
481
|
+
const first = side === 'left' || side === 'top' ? newLeaf : sourceUpdated;
|
|
482
|
+
const second = side === 'left' || side === 'top' ? sourceUpdated : newLeaf;
|
|
483
|
+
const split = {
|
|
484
|
+
kind: 'split',
|
|
485
|
+
direction,
|
|
486
|
+
ratio: 0.5,
|
|
487
|
+
first,
|
|
488
|
+
second,
|
|
489
|
+
};
|
|
490
|
+
const next = replaceLeaf(tree, leafId, split);
|
|
491
|
+
setTabPanelTree(ctx, next);
|
|
492
|
+
ctx.host?.closeMenu();
|
|
493
|
+
}
|
|
494
|
+
// For TabPanelLayout: split the right-clicked GROUP (leaf), placing the
|
|
495
|
+
// existing leaf on one side and an empty leaf on the other.
|
|
496
|
+
function splitGroup(ctx, direction, newSide) {
|
|
497
|
+
const leafId = ctxGroup(ctx);
|
|
498
|
+
if (!leafId)
|
|
499
|
+
return;
|
|
500
|
+
const state = surfaceState(ctx);
|
|
501
|
+
const tree = state?.tabPanel?.tree;
|
|
502
|
+
if (!tree)
|
|
503
|
+
return;
|
|
504
|
+
const leaf = findLeaf(tree, leafId);
|
|
505
|
+
if (!leaf)
|
|
506
|
+
return;
|
|
507
|
+
const empty = {
|
|
508
|
+
kind: 'leaf',
|
|
509
|
+
id: newLeafId(),
|
|
510
|
+
tabs: [],
|
|
511
|
+
activeKey: undefined,
|
|
512
|
+
};
|
|
513
|
+
const split = {
|
|
514
|
+
kind: 'split',
|
|
515
|
+
direction,
|
|
516
|
+
ratio: 0.5,
|
|
517
|
+
first: newSide === 'first' ? empty : leaf,
|
|
518
|
+
second: newSide === 'first' ? leaf : empty,
|
|
519
|
+
};
|
|
520
|
+
const next = replaceLeaf(tree, leafId, split);
|
|
521
|
+
setTabPanelTree(ctx, next);
|
|
522
|
+
ctx.host?.closeMenu();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Returns the layout-switch commands as a flat array so they can be spread
|
|
526
|
+
// into the main subs list.
|
|
527
|
+
function layoutSwitchCommands(scena) {
|
|
528
|
+
const targets = [
|
|
529
|
+
{ id: 'surface.setLayout.tab', title: 'Layout: Tabs', layoutId: 'tab' },
|
|
530
|
+
{ id: 'surface.setLayout.tabPanel', title: 'Layout: Tab groups', layoutId: 'tab-panel' },
|
|
531
|
+
{ id: 'surface.setLayout.split', title: 'Layout: Split', layoutId: 'split' },
|
|
532
|
+
{ id: 'surface.setLayout.spatial', title: 'Layout: Spatial', layoutId: 'spatial' },
|
|
533
|
+
{ id: 'surface.setLayout.stack', title: 'Layout: Stack', layoutId: 'stack' },
|
|
534
|
+
{ id: 'surface.setLayout.single', title: 'Layout: Single', layoutId: 'single' },
|
|
535
|
+
];
|
|
536
|
+
return targets.map((t) => scena.commands.register({
|
|
537
|
+
id: t.id,
|
|
538
|
+
title: t.title,
|
|
539
|
+
slots: ['surface:context'],
|
|
540
|
+
when: `$/surface/layoutId != "${t.layoutId}"`,
|
|
541
|
+
run: (ctx) => {
|
|
542
|
+
const surface = (ctx.store.get('$/surface/name') ?? 'main');
|
|
543
|
+
const cur = ctx.scena.layout.get().surfaces[surface];
|
|
544
|
+
ctx.scena.layout.setSurface(surface, { ...cur, layout: t.layoutId });
|
|
545
|
+
ctx.host?.closeMenu();
|
|
546
|
+
},
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Scena } from '../types/scena.js';
|
|
2
|
+
import type { Disposable } from '../types/disposable.js';
|
|
3
|
+
import type { CommandContext } from '../types/command.js';
|
|
4
|
+
import type { ComponentDefinition } from '../types/component-registry.js';
|
|
5
|
+
import type { SurfaceName } from '../types/mount-surface.js';
|
|
6
|
+
export interface RegisterOpenerCommandsOptions {
|
|
7
|
+
resourceKind: string;
|
|
8
|
+
slots?: string[];
|
|
9
|
+
category?: string;
|
|
10
|
+
commandIdPrefix?: string;
|
|
11
|
+
surface?: SurfaceName;
|
|
12
|
+
mountKey?: (resourceId: string, viewerComponent: string) => string;
|
|
13
|
+
resourceProps?: (resourceId: string) => Record<string, unknown>;
|
|
14
|
+
resourceTitle?: (resourceId: string) => string;
|
|
15
|
+
onOpen?: (ctx: CommandContext, viewer: ComponentDefinition, resourceId: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function registerOpenerCommands(scena: Scena, opts: RegisterOpenerCommandsOptions): Disposable;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { disposableFrom } from '../core/disposable.js';
|
|
2
|
+
import { resolveLabel } from '../core/label.js';
|
|
3
|
+
import { translate } from '../i18n/registry.js';
|
|
4
|
+
export function registerOpenerCommands(scena, opts) {
|
|
5
|
+
const slots = opts.slots ?? ['file:context'];
|
|
6
|
+
const category = opts.category ?? 'Open with';
|
|
7
|
+
const surface = opts.surface ?? 'main';
|
|
8
|
+
const mountKey = opts.mountKey ?? ((id, comp) => `${opts.resourceKind}:${comp}:${id}`);
|
|
9
|
+
const commandIdPrefix = opts.commandIdPrefix ?? `openWith.${opts.resourceKind}`;
|
|
10
|
+
const resourceProps = opts.resourceProps ?? ((id) => ({ path: id }));
|
|
11
|
+
const resourceTitle = opts.resourceTitle ?? ((id) => id);
|
|
12
|
+
const kindGuard = `$/resource/kind == "${opts.resourceKind}"`;
|
|
13
|
+
// viewer component id → disposable of its generated command. Used both
|
|
14
|
+
// to dispose departed viewers' commands and to skip re-registering ones
|
|
15
|
+
// that are still around on resync.
|
|
16
|
+
const active = new Map();
|
|
17
|
+
function registerFor(def) {
|
|
18
|
+
// ComponentOpensSpec.selector is a WhenClause (`/...` paths). The when
|
|
19
|
+
// engine reads `$/...` paths, so we rewrite the leading `/` to `$/` if
|
|
20
|
+
// the caller didn't already use the absolute form. A prior version
|
|
21
|
+
// also rewrote later embedded paths but accidentally double-prefixed
|
|
22
|
+
// the leading `$/` to `$$/`, which the when parser couldn't resolve —
|
|
23
|
+
// breaking single-path selectors like `/resource/ext == "md"`.
|
|
24
|
+
const sel = def.opens?.selector;
|
|
25
|
+
const selWhen = typeof sel === 'string' ? (sel.startsWith('$/') ? sel : sel.replace(/^\//, '$/')) : null;
|
|
26
|
+
const when = selWhen ? `${kindGuard} && ${selWhen}` : kindGuard;
|
|
27
|
+
const sub = scena.commands.register({
|
|
28
|
+
id: `${commandIdPrefix}.${def.component}`,
|
|
29
|
+
title: resolveLabel(def.opens?.title ?? def.component, {
|
|
30
|
+
get: (path) => {
|
|
31
|
+
// Expose the resource's properties for label resolution (e.g.
|
|
32
|
+
// `{ path }` bindings in the title can read `$/resource/path`).
|
|
33
|
+
if (path.startsWith('$/'))
|
|
34
|
+
return scena.store.get(path);
|
|
35
|
+
return undefined;
|
|
36
|
+
},
|
|
37
|
+
translate: (key) => translate(key),
|
|
38
|
+
}) ?? def.component,
|
|
39
|
+
icon: def.opens?.icon,
|
|
40
|
+
color: def.opens?.color,
|
|
41
|
+
category,
|
|
42
|
+
slots,
|
|
43
|
+
when,
|
|
44
|
+
run: (ctx) => {
|
|
45
|
+
const id = ctx.store.get('$/resource/id');
|
|
46
|
+
if (!id)
|
|
47
|
+
return;
|
|
48
|
+
ctx.scena.surfaces.open({
|
|
49
|
+
surface,
|
|
50
|
+
key: mountKey(id, def.component),
|
|
51
|
+
resource: { component: def.component, ...resourceProps(id) },
|
|
52
|
+
// Tab/section header gets the resource's display name + the
|
|
53
|
+
// viewer's own icon/color so e.g. a Markdown-opened .md file
|
|
54
|
+
// tab shows the filename in violet with the memo glyph.
|
|
55
|
+
props: {
|
|
56
|
+
title: resourceTitle(id),
|
|
57
|
+
icon: def.opens?.icon,
|
|
58
|
+
color: def.opens?.color,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
opts.onOpen?.(ctx, def, id);
|
|
62
|
+
ctx.host?.closeMenu();
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
active.set(def.component, sub);
|
|
66
|
+
}
|
|
67
|
+
function sync() {
|
|
68
|
+
const present = new Set();
|
|
69
|
+
for (const def of scena.components.findOpeners(opts.resourceKind)) {
|
|
70
|
+
present.add(def.component);
|
|
71
|
+
if (active.has(def.component))
|
|
72
|
+
continue; // already registered
|
|
73
|
+
registerFor(def);
|
|
74
|
+
}
|
|
75
|
+
// Dispose commands for viewers no longer present.
|
|
76
|
+
for (const [comp, sub] of active) {
|
|
77
|
+
if (present.has(comp))
|
|
78
|
+
continue;
|
|
79
|
+
sub.dispose();
|
|
80
|
+
active.delete(comp);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Initial pass, then subscribe to component-registry changes.
|
|
84
|
+
sync();
|
|
85
|
+
const sub = scena.events.on('scena:registry:changed', (payload) => {
|
|
86
|
+
if (payload.registry !== 'components')
|
|
87
|
+
return;
|
|
88
|
+
sync();
|
|
89
|
+
});
|
|
90
|
+
return disposableFrom(() => {
|
|
91
|
+
sub.dispose();
|
|
92
|
+
for (const s of active.values())
|
|
93
|
+
s.dispose();
|
|
94
|
+
active.clear();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Disposable } from '../types/disposable.js';
|
|
2
|
+
import type { EventBus } from '../types/events.js';
|
|
3
|
+
import type { ReactiveStore, SocketBridge } from '../types/reactive-store.js';
|
|
4
|
+
import type { MountSurfaceRegistry, SurfaceName } from '../types/mount-surface.js';
|
|
5
|
+
import type { ConverterRegistry } from '../types/converter-registry.js';
|
|
6
|
+
interface Deps {
|
|
7
|
+
store: ReactiveStore;
|
|
8
|
+
surfaces: MountSurfaceRegistry;
|
|
9
|
+
converters: ConverterRegistry;
|
|
10
|
+
socket: SocketBridge;
|
|
11
|
+
events: EventBus;
|
|
12
|
+
defaultSurface?: SurfaceName;
|
|
13
|
+
}
|
|
14
|
+
export declare function createSurfaceBridge(deps: Deps): Disposable;
|
|
15
|
+
export {};
|