@cosmicdrift/kumiko-renderer 0.374.0 → 0.377.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/app/action-form-support.d.ts +9 -2
- package/dist/app/action-form-support.js +24 -2
- package/dist/app/client-actions.d.ts +2 -2
- package/dist/app/column-renderers.js +1 -1
- package/dist/app/config-edit-shim.js +1 -1
- package/dist/app/content-preview.js +1 -6
- package/dist/app/custom-screens.js +1 -1
- package/dist/app/dashboard-body.d.ts +1 -1
- package/dist/app/entity-list-expanded-row.d.ts +1 -1
- package/dist/app/form-schema.d.ts +1 -1
- package/dist/app/form-schema.js +2 -2
- package/dist/app/kumiko-screen.d.ts +1 -1
- package/dist/app/kumiko-screen.js +7 -6
- package/dist/app/list-empty-content.d.ts +1 -1
- package/dist/app/list-facets.d.ts +1 -1
- package/dist/app/nav.js +2 -2
- package/dist/app/query-state-banners.d.ts +1 -1
- package/dist/app/reference-link.d.ts +1 -1
- package/dist/app/row-actions.d.ts +1 -1
- package/dist/app/secret-mint-body.d.ts +1 -1
- package/dist/app/secrets-edit-body.d.ts +1 -1
- package/dist/app/use-record-actions.d.ts +1 -1
- package/dist/app/write-failed-error.d.ts +1 -1
- package/dist/components/availability-options-select.d.ts +1 -1
- package/dist/components/embedded-list-field.d.ts +1 -1
- package/dist/components/embedded-list-field.js +1 -1
- package/dist/components/file-list-field.d.ts +1 -1
- package/dist/components/grid-cell-for-field.d.ts +1 -1
- package/dist/components/multi-select-checkboxes.d.ts +1 -1
- package/dist/components/multi-select-checkboxes.js +1 -1
- package/dist/components/query-options-select.d.ts +1 -1
- package/dist/components/related-list-section.d.ts +1 -1
- package/dist/components/render-edit-logic.d.ts +1 -1
- package/dist/components/render-edit-types.d.ts +2 -2
- package/dist/components/render-edit.d.ts +1 -1
- package/dist/components/render-edit.js +4 -2
- package/dist/components/render-field.d.ts +1 -1
- package/dist/components/render-field.js +4 -1
- package/dist/components/render-list.d.ts +1 -1
- package/dist/components/render-list.js +20 -4
- package/dist/components/write-form-section.d.ts +1 -1
- package/dist/components/write-form-section.js +1 -1
- package/dist/context/dispatcher-context.d.ts +1 -1
- package/dist/format-when.js +1 -1
- package/dist/headless/apex/css.d.ts +3 -0
- package/dist/headless/apex/css.js +298 -0
- package/dist/headless/apex/index.d.ts +236 -0
- package/dist/headless/apex/index.js +377 -0
- package/dist/headless/apex/lightbox.d.ts +4 -0
- package/dist/headless/apex/lightbox.js +81 -0
- package/dist/headless/contracts/asset.d.ts +15 -0
- package/dist/headless/contracts/asset.js +17 -0
- package/dist/headless/contracts/index.d.ts +3 -0
- package/dist/headless/contracts/index.js +1 -0
- package/dist/headless/contracts/locale.d.ts +7 -0
- package/dist/headless/contracts/locale.js +11 -0
- package/dist/headless/contracts/primitives.d.ts +93 -0
- package/dist/headless/contracts/primitives.js +21 -0
- package/dist/headless/dispatcher/index.d.ts +2 -0
- package/dist/headless/dispatcher/index.js +1 -0
- package/dist/headless/dispatcher/types.d.ts +86 -0
- package/dist/headless/dispatcher/types.js +34 -0
- package/dist/headless/form/form-controller.d.ts +2 -0
- package/dist/headless/form/form-controller.js +409 -0
- package/dist/headless/form/index.d.ts +3 -0
- package/dist/headless/form/index.js +2 -0
- package/dist/headless/form/types.d.ts +76 -0
- package/dist/headless/form/types.js +1 -0
- package/dist/headless/form/zod-bridge.d.ts +10 -0
- package/dist/headless/form/zod-bridge.js +103 -0
- package/dist/headless/format/document-values.d.ts +12 -0
- package/dist/headless/format/document-values.js +70 -0
- package/dist/headless/format/escape.d.ts +5 -0
- package/dist/headless/format/escape.js +75 -0
- package/dist/headless/format/html-template.d.ts +9 -0
- package/dist/headless/format/html-template.js +39 -0
- package/dist/headless/format/index.d.ts +12 -0
- package/dist/headless/format/index.js +237 -0
- package/dist/headless/format/money.d.ts +3 -0
- package/dist/headless/format/money.js +39 -0
- package/dist/headless/index.d.ts +14 -0
- package/dist/headless/index.js +9 -0
- package/dist/headless/locale-routing/index.d.ts +41 -0
- package/dist/headless/locale-routing/index.js +145 -0
- package/dist/headless/nav/index.d.ts +2 -0
- package/dist/headless/nav/index.js +1 -0
- package/dist/headless/nav/resolve.d.ts +2 -0
- package/dist/headless/nav/resolve.js +71 -0
- package/dist/headless/nav/types.d.ts +26 -0
- package/dist/headless/nav/types.js +1 -0
- package/dist/headless/store/create-store.d.ts +2 -0
- package/dist/headless/store/create-store.js +43 -0
- package/dist/headless/store/equality.d.ts +1 -0
- package/dist/headless/store/equality.js +34 -0
- package/dist/headless/store/index.d.ts +3 -0
- package/dist/headless/store/index.js +2 -0
- package/dist/headless/store/types.d.ts +7 -0
- package/dist/headless/store/types.js +10 -0
- package/dist/headless/view-model/edit.d.ts +18 -0
- package/dist/headless/view-model/edit.js +501 -0
- package/dist/headless/view-model/embedded-list.d.ts +27 -0
- package/dist/headless/view-model/embedded-list.js +63 -0
- package/dist/headless/view-model/index.d.ts +7 -0
- package/dist/headless/view-model/index.js +3 -0
- package/dist/headless/view-model/list-file-display.d.ts +3 -0
- package/dist/headless/view-model/list-file-display.js +27 -0
- package/dist/headless/view-model/list.d.ts +16 -0
- package/dist/headless/view-model/list.js +229 -0
- package/dist/headless/view-model/types.d.ts +382 -0
- package/dist/headless/view-model/types.js +1 -0
- package/dist/hooks/use-ai-text.d.ts +1 -1
- package/dist/hooks/use-form.d.ts +1 -1
- package/dist/hooks/use-form.js +1 -1
- package/dist/hooks/use-mutation.d.ts +1 -1
- package/dist/hooks/use-query.d.ts +1 -1
- package/dist/hooks/use-store.d.ts +1 -1
- package/dist/hooks/use-store.js +7 -7
- package/dist/hooks/use-stream-handler.d.ts +1 -1
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +1 -1
- package/dist/index.js +4 -4
- package/dist/live/csrf.d.ts +2 -0
- package/dist/live/csrf.js +60 -0
- package/dist/live/dispatcher-live.d.ts +7 -0
- package/dist/live/dispatcher-live.js +286 -0
- package/dist/live/envelopes.d.ts +101 -0
- package/dist/live/envelopes.js +35 -0
- package/dist/live/error-mapping.d.ts +16 -0
- package/dist/live/error-mapping.js +82 -0
- package/dist/live/index.d.ts +7 -0
- package/dist/live/index.js +5 -0
- package/dist/live/locale.d.ts +2 -0
- package/dist/live/locale.js +25 -0
- package/dist/live/sse-stream.d.ts +14 -0
- package/dist/live/sse-stream.js +128 -0
- package/dist/primitives.d.ts +35 -36
- package/dist/primitives.js +1 -1
- package/dist/session/session-ended.d.ts +1 -1
- package/dist/tokens.d.ts +3 -4
- package/dist/tokens.js +4 -5
- package/dist/web/app/app-schema-boot.d.ts +19 -0
- package/dist/web/app/app-schema-boot.js +124 -0
- package/dist/web/app/browser-locale.d.ts +22 -0
- package/dist/web/app/browser-locale.js +78 -0
- package/dist/web/app/client-plugin.d.ts +106 -0
- package/dist/web/app/client-plugin.js +12 -0
- package/dist/web/app/create-app.d.ts +104 -0
- package/dist/web/app/create-app.js +498 -0
- package/dist/web/app/create-public-surface.d.ts +36 -0
- package/dist/web/app/create-public-surface.js +40 -0
- package/dist/web/app/dashboard-body.d.ts +3 -0
- package/dist/web/app/dashboard-body.js +865 -0
- package/dist/web/app/dashboard-refresh.d.ts +6 -0
- package/dist/web/app/dashboard-refresh.js +15 -0
- package/dist/web/app/document-lang-sync.d.ts +4 -0
- package/dist/web/app/document-lang-sync.js +27 -0
- package/dist/web/app/draft-storage.d.ts +11 -0
- package/dist/web/app/draft-storage.js +53 -0
- package/dist/web/app/nav-providers-context.d.ts +13 -0
- package/dist/web/app/nav-providers-context.js +18 -0
- package/dist/web/app/nav.d.ts +40 -0
- package/dist/web/app/nav.js +231 -0
- package/dist/web/app/plain-content-editor.d.ts +3 -0
- package/dist/web/app/plain-content-editor.js +30 -0
- package/dist/web/app/relative-time.d.ts +10 -0
- package/dist/web/app/relative-time.js +56 -0
- package/dist/web/app/resolvers-context.d.ts +12 -0
- package/dist/web/app/resolvers-context.js +10 -0
- package/dist/web/app/rich-content-editor.d.ts +3 -0
- package/dist/web/app/rich-content-editor.js +15 -0
- package/dist/web/app/root-error-fallback.d.ts +2 -0
- package/dist/web/app/root-error-fallback.js +21 -0
- package/dist/web/app/screen-error-fallback.d.ts +6 -0
- package/dist/web/app/screen-error-fallback.js +17 -0
- package/dist/web/app/tiptap-editor.d.ts +3 -0
- package/dist/web/app/tiptap-editor.js +120 -0
- package/dist/web/app/toast-action-notifier.d.ts +5 -0
- package/dist/web/app/toast-action-notifier.js +17 -0
- package/dist/web/components/config-cascade.d.ts +13 -0
- package/dist/web/components/config-cascade.js +155 -0
- package/dist/web/components/config-source-badge.d.ts +6 -0
- package/dist/web/components/config-source-badge.js +55 -0
- package/dist/web/default-color-scheme.d.ts +2 -0
- package/dist/web/default-color-scheme.js +3 -0
- package/dist/web/icons.d.ts +94 -0
- package/dist/web/icons.js +100 -0
- package/dist/web/index.d.ts +78 -0
- package/dist/web/index.js +53 -0
- package/dist/web/layout/app-layout.d.ts +21 -0
- package/dist/web/layout/app-layout.js +8 -0
- package/dist/web/layout/avatar.d.ts +12 -0
- package/dist/web/layout/avatar.js +63 -0
- package/dist/web/layout/default-app-shell.d.ts +41 -0
- package/dist/web/layout/default-app-shell.js +16 -0
- package/dist/web/layout/editor-panel.d.ts +10 -0
- package/dist/web/layout/editor-panel.js +29 -0
- package/dist/web/layout/fill-classes.d.ts +9 -0
- package/dist/web/layout/fill-classes.js +15 -0
- package/dist/web/layout/filter-app-schema-navs.d.ts +7 -0
- package/dist/web/layout/filter-app-schema-navs.js +68 -0
- package/dist/web/layout/header-action-group.d.ts +6 -0
- package/dist/web/layout/header-action-group.js +8 -0
- package/dist/web/layout/header-overflow-menu.d.ts +4 -0
- package/dist/web/layout/header-overflow-menu.js +8 -0
- package/dist/web/layout/language-menu-items.d.ts +8 -0
- package/dist/web/layout/language-menu-items.js +16 -0
- package/dist/web/layout/language-switcher.d.ts +25 -0
- package/dist/web/layout/language-switcher.js +46 -0
- package/dist/web/layout/nav-tree.d.ts +19 -0
- package/dist/web/layout/nav-tree.js +536 -0
- package/dist/web/layout/page-header-slot.d.ts +25 -0
- package/dist/web/layout/page-header-slot.js +50 -0
- package/dist/web/layout/profile-menu.d.ts +13 -0
- package/dist/web/layout/profile-menu.js +6 -0
- package/dist/web/layout/shell-breadcrumb.d.ts +11 -0
- package/dist/web/layout/shell-breadcrumb.js +34 -0
- package/dist/web/layout/shell-header.d.ts +12 -0
- package/dist/web/layout/shell-header.js +159 -0
- package/dist/web/layout/sidebar-brand.d.ts +15 -0
- package/dist/web/layout/sidebar-brand.js +9 -0
- package/dist/web/layout/sidebar-panel.d.ts +41 -0
- package/dist/web/layout/sidebar-panel.js +107 -0
- package/dist/web/layout/sidebar-user.d.ts +8 -0
- package/dist/web/layout/sidebar-user.js +17 -0
- package/dist/web/layout/sidebar.d.ts +17 -0
- package/dist/web/layout/sidebar.js +11 -0
- package/dist/web/layout/target-resolver-stub.d.ts +13 -0
- package/dist/web/layout/target-resolver-stub.js +61 -0
- package/dist/web/layout/target-url.d.ts +12 -0
- package/dist/web/layout/target-url.js +77 -0
- package/dist/web/layout/theme-label.d.ts +15 -0
- package/dist/web/layout/theme-label.js +35 -0
- package/dist/web/layout/theme-menu-item.d.ts +12 -0
- package/dist/web/layout/theme-menu-item.js +16 -0
- package/dist/web/layout/theme-toggle.d.ts +17 -0
- package/dist/web/layout/theme-toggle.js +22 -0
- package/dist/web/layout/topbar.d.ts +8 -0
- package/dist/web/layout/topbar.js +4 -0
- package/dist/web/layout/workspace-shell.d.ts +63 -0
- package/dist/web/layout/workspace-shell.js +220 -0
- package/dist/web/layout/workspace-switcher.d.ts +15 -0
- package/dist/web/layout/workspace-switcher.js +36 -0
- package/dist/web/lib/accept-attr.d.ts +1 -0
- package/dist/web/lib/accept-attr.js +6 -0
- package/dist/web/lib/clamp.d.ts +1 -0
- package/dist/web/lib/clamp.js +3 -0
- package/dist/web/lib/cn.d.ts +6 -0
- package/dist/web/lib/cn.js +9 -0
- package/dist/web/lib/download.d.ts +2 -0
- package/dist/web/lib/download.js +24 -0
- package/dist/web/lib/file-ref-download.d.ts +15 -0
- package/dist/web/lib/file-ref-download.js +62 -0
- package/dist/web/lib/file-upload-form-data.d.ts +7 -0
- package/dist/web/lib/file-upload-form-data.js +11 -0
- package/dist/web/lib/resize-image.d.ts +1 -0
- package/dist/web/lib/resize-image.js +66 -0
- package/dist/web/lib/upload-file-ref.d.ts +9 -0
- package/dist/web/lib/upload-file-ref.js +29 -0
- package/dist/web/primitives/action-menu.d.ts +34 -0
- package/dist/web/primitives/action-menu.js +30 -0
- package/dist/web/primitives/calendar-popover.d.ts +13 -0
- package/dist/web/primitives/calendar-popover.js +76 -0
- package/dist/web/primitives/combobox.d.ts +56 -0
- package/dist/web/primitives/combobox.js +127 -0
- package/dist/web/primitives/date-field.d.ts +18 -0
- package/dist/web/primitives/date-field.js +74 -0
- package/dist/web/primitives/date-input.d.ts +1 -0
- package/dist/web/primitives/date-input.js +5 -0
- package/dist/web/primitives/date-parse.d.ts +11 -0
- package/dist/web/primitives/date-parse.js +159 -0
- package/dist/web/primitives/dialog.d.ts +3 -0
- package/dist/web/primitives/dialog.js +42 -0
- package/dist/web/primitives/drawer.d.ts +3 -0
- package/dist/web/primitives/drawer.js +17 -0
- package/dist/web/primitives/dropdown-menu.d.ts +15 -0
- package/dist/web/primitives/dropdown-menu.js +56 -0
- package/dist/web/primitives/embedded-list-input.d.ts +9 -0
- package/dist/web/primitives/embedded-list-input.js +382 -0
- package/dist/web/primitives/file-thumbnail.d.ts +25 -0
- package/dist/web/primitives/file-thumbnail.js +90 -0
- package/dist/web/primitives/file-upload.d.ts +21 -0
- package/dist/web/primitives/file-upload.js +56 -0
- package/dist/web/primitives/index.d.ts +34 -0
- package/dist/web/primitives/index.js +2021 -0
- package/dist/web/primitives/json-view.d.ts +7 -0
- package/dist/web/primitives/json-view.js +84 -0
- package/dist/web/primitives/layout.d.ts +27 -0
- package/dist/web/primitives/layout.js +30 -0
- package/dist/web/primitives/lightbox.d.ts +3 -0
- package/dist/web/primitives/lightbox.js +36 -0
- package/dist/web/primitives/locale-number.d.ts +2 -0
- package/dist/web/primitives/locale-number.js +54 -0
- package/dist/web/primitives/located-timestamp-input.d.ts +22 -0
- package/dist/web/primitives/located-timestamp-input.js +22 -0
- package/dist/web/primitives/metric.d.ts +5 -0
- package/dist/web/primitives/metric.js +18 -0
- package/dist/web/primitives/modal-shell.d.ts +17 -0
- package/dist/web/primitives/modal-shell.js +9 -0
- package/dist/web/primitives/modal.d.ts +3 -0
- package/dist/web/primitives/modal.js +13 -0
- package/dist/web/primitives/money-input.d.ts +18 -0
- package/dist/web/primitives/money-input.js +122 -0
- package/dist/web/primitives/narrow-pinned-footer.d.ts +12 -0
- package/dist/web/primitives/narrow-pinned-footer.js +153 -0
- package/dist/web/primitives/number-input.d.ts +18 -0
- package/dist/web/primitives/number-input.js +53 -0
- package/dist/web/primitives/option-groups.d.ts +11 -0
- package/dist/web/primitives/option-groups.js +27 -0
- package/dist/web/primitives/page-header.d.ts +3 -0
- package/dist/web/primitives/page-header.js +52 -0
- package/dist/web/primitives/promo-panel.d.ts +3 -0
- package/dist/web/primitives/promo-panel.js +11 -0
- package/dist/web/primitives/segmented-select.d.ts +21 -0
- package/dist/web/primitives/segmented-select.js +62 -0
- package/dist/web/primitives/share-actions.d.ts +4 -0
- package/dist/web/primitives/share-actions.js +56 -0
- package/dist/web/primitives/status-badge.d.ts +3 -0
- package/dist/web/primitives/status-badge.js +5 -0
- package/dist/web/primitives/sticky-action-bar.d.ts +5 -0
- package/dist/web/primitives/sticky-action-bar.js +15 -0
- package/dist/web/primitives/tabs.d.ts +3 -0
- package/dist/web/primitives/tabs.js +107 -0
- package/dist/web/primitives/timestamp-input.d.ts +27 -0
- package/dist/web/primitives/timestamp-input.js +87 -0
- package/dist/web/primitives/toast.d.ts +19 -0
- package/dist/web/primitives/toast.js +63 -0
- package/dist/web/primitives/tz-input.d.ts +11 -0
- package/dist/web/primitives/tz-input.js +8 -0
- package/dist/web/primitives/tz-options.d.ts +4 -0
- package/dist/web/primitives/tz-options.js +17 -0
- package/dist/web/primitives/use-keyboard-inset.d.ts +1 -0
- package/dist/web/primitives/use-keyboard-inset.js +28 -0
- package/dist/web/primitives/use-narrow-viewport.d.ts +4 -0
- package/dist/web/primitives/use-narrow-viewport.js +46 -0
- package/dist/web/react-css-custom-properties.d.ts +6 -0
- package/dist/web/react-css-custom-properties.js +1 -0
- package/dist/web/sse/live-events.d.ts +15 -0
- package/dist/web/sse/live-events.js +148 -0
- package/dist/web/theme-plugin.d.ts +41 -0
- package/dist/web/theme-plugin.js +123 -0
- package/dist/web/tokens.d.ts +24 -0
- package/dist/web/tokens.js +203 -0
- package/dist/web/ui/avatar.d.ts +11 -0
- package/dist/web/ui/avatar.js +25 -0
- package/dist/web/ui/badge.d.ts +9 -0
- package/dist/web/ui/badge.js +26 -0
- package/dist/web/ui/breadcrumb.d.ts +11 -0
- package/dist/web/ui/breadcrumb.js +29 -0
- package/dist/web/ui/button.d.ts +10 -0
- package/dist/web/ui/button.js +37 -0
- package/dist/web/ui/checkbox.d.ts +4 -0
- package/dist/web/ui/checkbox.js +11 -0
- package/dist/web/ui/input.d.ts +3 -0
- package/dist/web/ui/input.js +8 -0
- package/dist/web/ui/label.d.ts +4 -0
- package/dist/web/ui/label.js +10 -0
- package/dist/web/ui/resizable.d.ts +7 -0
- package/dist/web/ui/resizable.js +16 -0
- package/dist/web/ui/separator.d.ts +4 -0
- package/dist/web/ui/separator.js +10 -0
- package/dist/web/ui/sheet.d.ts +14 -0
- package/dist/web/ui/sheet.js +42 -0
- package/dist/web/ui/sidebar.d.ts +73 -0
- package/dist/web/ui/sidebar.js +211 -0
- package/dist/web/ui/skeleton.d.ts +2 -0
- package/dist/web/ui/skeleton.js +7 -0
- package/dist/web/ui/switch.d.ts +6 -0
- package/dist/web/ui/switch.js +10 -0
- package/dist/web/ui/table.d.ts +10 -0
- package/dist/web/ui/table.js +30 -0
- package/dist/web/ui/tabs.d.ts +11 -0
- package/dist/web/ui/tabs.js +31 -0
- package/dist/web/ui/textarea.d.ts +3 -0
- package/dist/web/ui/textarea.js +8 -0
- package/dist/web/ui/tooltip.d.ts +7 -0
- package/dist/web/ui/tooltip.js +19 -0
- package/dist/web/ui/use-mobile.d.ts +1 -0
- package/dist/web/ui/use-mobile.js +16 -0
- package/dist/web/version/update-checker.d.ts +9 -0
- package/dist/web/version/update-checker.js +88 -0
- package/dist/web/widgets/ai-text-field.d.ts +36 -0
- package/dist/web/widgets/ai-text-field.js +137 -0
- package/dist/web/widgets/chart-window.d.ts +58 -0
- package/dist/web/widgets/chart-window.js +190 -0
- package/dist/web/widgets/charts.d.ts +208 -0
- package/dist/web/widgets/charts.js +685 -0
- package/dist/web/widgets/collapsible-section.d.ts +10 -0
- package/dist/web/widgets/collapsible-section.js +17 -0
- package/dist/web/widgets/dashboard-list.d.ts +21 -0
- package/dist/web/widgets/dashboard-list.js +45 -0
- package/dist/web/widgets/detail-list.d.ts +15 -0
- package/dist/web/widgets/detail-list.js +8 -0
- package/dist/web/widgets/drawer.d.ts +65 -0
- package/dist/web/widgets/drawer.js +142 -0
- package/dist/web/widgets/feed-list.d.ts +13 -0
- package/dist/web/widgets/feed-list.js +9 -0
- package/dist/web/widgets/floating-panel.d.ts +30 -0
- package/dist/web/widgets/floating-panel.js +242 -0
- package/dist/web/widgets/form-fields.d.ts +93 -0
- package/dist/web/widgets/form-fields.js +50 -0
- package/dist/web/widgets/index.d.ts +27 -0
- package/dist/web/widgets/index.js +30 -0
- package/dist/web/widgets/infinity-list.d.ts +46 -0
- package/dist/web/widgets/infinity-list.js +211 -0
- package/dist/web/widgets/mode-switch.d.ts +21 -0
- package/dist/web/widgets/mode-switch.js +25 -0
- package/dist/web/widgets/photo-slots.d.ts +29 -0
- package/dist/web/widgets/photo-slots.js +52 -0
- package/dist/web/widgets/plan-card.d.ts +36 -0
- package/dist/web/widgets/plan-card.js +13 -0
- package/dist/web/widgets/progress-bar.d.ts +19 -0
- package/dist/web/widgets/progress-bar.js +24 -0
- package/dist/web/widgets/progress-list.d.ts +19 -0
- package/dist/web/widgets/progress-list.js +11 -0
- package/dist/web/widgets/public-shell.d.ts +22 -0
- package/dist/web/widgets/public-shell.js +15 -0
- package/dist/web/widgets/query-table.d.ts +29 -0
- package/dist/web/widgets/query-table.js +30 -0
- package/dist/web/widgets/result-panel.d.ts +69 -0
- package/dist/web/widgets/result-panel.js +30 -0
- package/dist/web/widgets/section-card.d.ts +12 -0
- package/dist/web/widgets/section-card.js +9 -0
- package/dist/web/widgets/sheet-parts.d.ts +9 -0
- package/dist/web/widgets/sheet-parts.js +11 -0
- package/dist/web/widgets/side-by-side-table.d.ts +31 -0
- package/dist/web/widgets/side-by-side-table.js +11 -0
- package/dist/web/widgets/stat.d.ts +54 -0
- package/dist/web/widgets/stat.js +65 -0
- package/dist/web/widgets/states.d.ts +23 -0
- package/dist/web/widgets/states.js +20 -0
- package/dist/web/widgets/status-badge.d.ts +15 -0
- package/dist/web/widgets/status-badge.js +24 -0
- package/dist/web/widgets/step-bar.d.ts +35 -0
- package/dist/web/widgets/step-bar.js +60 -0
- package/dist/web/widgets/upload-zone.d.ts +24 -0
- package/dist/web/widgets/upload-zone.js +108 -0
- package/dist/web/widgets/use-draft.d.ts +20 -0
- package/dist/web/widgets/use-draft.js +21 -0
- package/dist/web/widgets/use-pointer-drag.d.ts +21 -0
- package/dist/web/widgets/use-pointer-drag.js +53 -0
- package/package.json +105 -7
- package/src/changes.json +53 -0
- package/src/web/fonts/OFL.txt +93 -0
- package/src/web/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
- package/src/web/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
- package/src/web/fonts/ibm-plex-mono-latin-ext-400.woff2 +0 -0
- package/src/web/fonts/ibm-plex-mono-latin-ext-500.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-400.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-500.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-600.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-ext-400.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-ext-500.woff2 +0 -0
- package/src/web/fonts/ibm-plex-sans-latin-ext-600.woff2 +0 -0
- package/src/web/styles.css +401 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { resolveNavParentScreen } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import { lastSegment } from "../../index.js";
|
|
3
|
+
export function screenTitleKey(screenShortId) {
|
|
4
|
+
return `screen:${screenShortId}.title`;
|
|
5
|
+
}
|
|
6
|
+
// Delegates to resolveNavParentScreen, supplying the feature-qualified-id
|
|
7
|
+
// normalization the client-side schema needs.
|
|
8
|
+
function resolveParentScreen(screens, detail) {
|
|
9
|
+
return resolveNavParentScreen(screens, detail, (s) => lastSegment(s.id));
|
|
10
|
+
}
|
|
11
|
+
/** The short id of {screenId}'s parent list screen, resolved via the same
|
|
12
|
+
* logic as `resolveDetailBreadcrumb` — `undefined` when nothing resolves
|
|
13
|
+
* (unknown screen, or no explicit/heuristic parent). */
|
|
14
|
+
export function resolveParentScreenId(screens, screenId) {
|
|
15
|
+
const detail = screens.find((s) => lastSegment(s.id) === screenId);
|
|
16
|
+
if (detail === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
const parent = resolveParentScreen(screens, detail);
|
|
19
|
+
return parent !== undefined ? lastSegment(parent.id) : undefined;
|
|
20
|
+
}
|
|
21
|
+
export function resolveDetailBreadcrumb(screens, detailScreenId, t) {
|
|
22
|
+
const detail = screens.find((s) => lastSegment(s.id) === detailScreenId);
|
|
23
|
+
if (detail === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
const list = resolveParentScreen(screens, detail);
|
|
26
|
+
if (list === undefined) {
|
|
27
|
+
return [{ label: t(screenTitleKey(detailScreenId)) }];
|
|
28
|
+
}
|
|
29
|
+
const listId = lastSegment(list.id);
|
|
30
|
+
return [
|
|
31
|
+
{ label: t(screenTitleKey(listId)), screenId: listId },
|
|
32
|
+
{ label: t(screenTitleKey(detailScreenId)) },
|
|
33
|
+
];
|
|
34
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { AppSchema, FeatureSchema } from "../../index.ts";
|
|
3
|
+
type ShellHeaderUser = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly roles: readonly string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function ShellHeader({ schema, user, headerActions, }: {
|
|
8
|
+
readonly schema: AppSchema | FeatureSchema;
|
|
9
|
+
readonly user?: ShellHeaderUser;
|
|
10
|
+
readonly headerActions?: ReactNode;
|
|
11
|
+
}): ReactNode;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// ShellHeader — inset header bar for sidebar-based shells: SidebarTrigger
|
|
3
|
+
// (rail/mobile-sheet toggle), breadcrumb of the active screen, optional
|
|
4
|
+
// right-aligned headerActions. Shared by DefaultAppShell and WorkspaceShell so
|
|
5
|
+
// both carry the same header (h-14, also with a collapsed icon rail).
|
|
6
|
+
//
|
|
7
|
+
// The `data-kumiko-layout="shell-header"` marker drives `--shell-header-height`
|
|
8
|
+
// in styles.css (:has() selector) — the single source for the header height
|
|
9
|
+
// that Drawer's `belowHeader` prop also reads.
|
|
10
|
+
import { MoreHorizontal } from "lucide-react";
|
|
11
|
+
import { Fragment, useEffect, useId, useMemo, useRef, useState, } from "react";
|
|
12
|
+
import { resolveNavigation } from "../../headless/index.js";
|
|
13
|
+
import { toAppSchema, useNav, usePageHeaderCompact, useTranslation, } from "../../index.js";
|
|
14
|
+
import { Icon, NAV_ICONS } from "../icons.js";
|
|
15
|
+
import { cn } from "../lib/cn.js";
|
|
16
|
+
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from "../ui/breadcrumb.js";
|
|
17
|
+
import { SidebarTrigger } from "../ui/sidebar.js";
|
|
18
|
+
import { HeaderOverflowMenuContext, headerOverflowMenuItemClass } from "./header-overflow-menu.js";
|
|
19
|
+
import { buildNavRegistrySliceForApp, lastSegment } from "./nav-tree.js";
|
|
20
|
+
import { usePageHeaderSlot } from "./page-header-slot.js";
|
|
21
|
+
import { resolveDetailBreadcrumb, screenTitleKey, } from "./shell-breadcrumb.js";
|
|
22
|
+
export function ShellHeader({ schema, user, headerActions, }) {
|
|
23
|
+
const nav = useNav();
|
|
24
|
+
const t = useTranslation();
|
|
25
|
+
const appSchema = toAppSchema(schema);
|
|
26
|
+
const tree = useMemo(() => {
|
|
27
|
+
const source = buildNavRegistrySliceForApp(appSchema);
|
|
28
|
+
return resolveNavigation({ source, ...(user !== undefined && { user }) });
|
|
29
|
+
}, [appSchema, user]);
|
|
30
|
+
const allScreens = useMemo(() => appSchema.features.flatMap((f) => f.screens), [appSchema.features]);
|
|
31
|
+
const slot = usePageHeaderSlot();
|
|
32
|
+
const standInScreenId = slot?.breadcrumbScreenId;
|
|
33
|
+
const screenId = standInScreenId ?? nav.route?.screenId;
|
|
34
|
+
const entityId = standInScreenId !== undefined ? undefined : nav.route?.entityId;
|
|
35
|
+
const crumbs = useMemo(() => {
|
|
36
|
+
if (screenId === undefined)
|
|
37
|
+
return undefined;
|
|
38
|
+
// A nav entry on an edit screen means "create"; an existing record (entityId in
|
|
39
|
+
// the route) is reached from the list, so the list crumb wins over the nav label.
|
|
40
|
+
if (entityId !== undefined) {
|
|
41
|
+
const detailCrumbs = resolveDetailBreadcrumb(allScreens, screenId, t);
|
|
42
|
+
if (detailCrumbs !== undefined && detailCrumbs.length > 1)
|
|
43
|
+
return detailCrumbs;
|
|
44
|
+
}
|
|
45
|
+
const navLabel = activeNavLabel(tree, screenId, (k) => t(k));
|
|
46
|
+
if (navLabel !== undefined)
|
|
47
|
+
return [{ label: navLabel }];
|
|
48
|
+
return resolveDetailBreadcrumb(allScreens, screenId, t);
|
|
49
|
+
}, [allScreens, screenId, entityId, t, tree]);
|
|
50
|
+
const setStatusElement = slot?.setStatusElement;
|
|
51
|
+
const setActionsElement = slot?.setActionsElement;
|
|
52
|
+
const titleOverride = slot?.title;
|
|
53
|
+
const recordTitle = slot?.recordTitle;
|
|
54
|
+
const compact = usePageHeaderCompact();
|
|
55
|
+
const overflowItems = slot?.overflowItems ?? [];
|
|
56
|
+
const hasOverflow = compact && (overflowItems.length > 0 || headerActions !== undefined);
|
|
57
|
+
// A stand-in without a translated title must not surface the raw i18n key.
|
|
58
|
+
const standInUntitled = standInScreenId !== undefined && crumbs?.at(-1)?.label === screenTitleKey(standInScreenId);
|
|
59
|
+
const shownCrumbs = (slot?.breadcrumbHidden === true && standInScreenId === undefined) || standInUntitled
|
|
60
|
+
? undefined
|
|
61
|
+
: crumbs !== undefined && recordTitle !== undefined && crumbs.length > 0
|
|
62
|
+
? [...crumbs.slice(0, -1), { label: recordTitle }, ...crumbs.slice(-1)]
|
|
63
|
+
: crumbs;
|
|
64
|
+
return (_jsxs("header", { "data-kumiko-layout": "shell-header", className: cn("flex h-14 shrink-0 items-center gap-3 border-b border-border pl-1.5 pr-3 md:pl-4 md:pr-6", compact && "relative"), children: [_jsxs("div", { className: cn("flex min-w-0 items-center gap-3", compact && "flex-1"), children: [_jsx(SidebarTrigger, { label: t("kumiko.sidebar.toggle") }), shownCrumbs !== undefined && shownCrumbs.length > 0 && (_jsx(Breadcrumb, { className: "min-w-0", "aria-label": t("kumiko.breadcrumb.label"), children: _jsx(BreadcrumbList, { className: "flex-nowrap gap-1.5 sm:gap-1.5", children: shownCrumbs.map((crumb, index) => {
|
|
65
|
+
const screenId = crumb.screenId;
|
|
66
|
+
const isLast = index === shownCrumbs.length - 1;
|
|
67
|
+
return (_jsxs(Fragment, { children: [index > 0 && (_jsx(BreadcrumbSeparator, { className: "hidden text-muted-foreground sm:inline-flex [&>svg]:size-3.5" })), _jsx(BreadcrumbItem, { className: cn("min-w-0", !isLast && "hidden sm:inline-flex"), children: screenId !== undefined && !isLast ? (_jsx(BreadcrumbLink, { href: "#", className: "whitespace-nowrap text-foreground-secondary", onClick: (e) => {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
nav.navigate({ screenId });
|
|
70
|
+
}, children: crumb.label })) : isLast ? (_jsx("h1", { "aria-current": "page", className: "truncate text-lg font-semibold text-foreground", children: titleOverride ?? crumb.label })) : (_jsx(BreadcrumbPage, { className: "whitespace-nowrap text-foreground-secondary", children: crumb.label })) })] }, screenId ?? crumb.label));
|
|
71
|
+
}) }) })), _jsx("div", { ref: setStatusElement, "data-kumiko-layout": "page-header-status", className: cn("flex items-center gap-2 empty:hidden", compact ? "min-w-0 overflow-hidden" : "shrink-0") })] }), _jsxs("div", { className: cn("ml-auto flex items-center gap-2", compact ? "min-w-0 max-w-[60%]" : "min-w-0 max-w-[50%] shrink-0 sm:max-w-none"), children: [_jsx("div", { ref: setActionsElement, "data-kumiko-layout": "page-header-actions", className: cn("flex min-w-0 items-center gap-2 empty:hidden [&_*]:min-w-0 [&_*]:max-w-full [&_button]:overflow-hidden [&_button]:whitespace-nowrap", compact && "overflow-hidden") }), !compact && headerActions !== undefined && (_jsx("div", { "data-kumiko-layout": "header-actions", className: "flex items-center gap-2", children: headerActions })), hasOverflow && _jsx(HeaderOverflow, { items: overflowItems, headerActions: headerActions })] })] }));
|
|
72
|
+
}
|
|
73
|
+
// A hand-rolled menu instead of Radix DropdownMenu: app headerActions are opaque
|
|
74
|
+
// nodes with their own popovers, and the panel stays mounted while closed so their
|
|
75
|
+
// effects (shortcut listeners) keep running.
|
|
76
|
+
const MENU_ITEM_SELECTOR = '[role="menuitem"]:not(:disabled)';
|
|
77
|
+
function HeaderOverflow({ items, headerActions, }) {
|
|
78
|
+
const t = useTranslation();
|
|
79
|
+
const [open, setOpen] = useState(false);
|
|
80
|
+
const panelId = useId();
|
|
81
|
+
const triggerRef = useRef(null);
|
|
82
|
+
const panelRef = useRef(null);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!open)
|
|
85
|
+
return;
|
|
86
|
+
panelRef.current?.querySelector(MENU_ITEM_SELECTOR)?.focus();
|
|
87
|
+
const onKeyDown = (event) => {
|
|
88
|
+
if (event.key !== "Escape")
|
|
89
|
+
return;
|
|
90
|
+
setOpen(false);
|
|
91
|
+
triggerRef.current?.focus();
|
|
92
|
+
};
|
|
93
|
+
const onPointerDown = (event) => {
|
|
94
|
+
const target = event.target;
|
|
95
|
+
if (!(target instanceof Node))
|
|
96
|
+
return;
|
|
97
|
+
if (panelRef.current?.contains(target) || triggerRef.current?.contains(target))
|
|
98
|
+
return;
|
|
99
|
+
setOpen(false);
|
|
100
|
+
};
|
|
101
|
+
document.addEventListener("keydown", onKeyDown);
|
|
102
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
103
|
+
return () => {
|
|
104
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
105
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
106
|
+
};
|
|
107
|
+
}, [open]);
|
|
108
|
+
const onPanelKeyDown = (event) => {
|
|
109
|
+
// Opaque app headerActions (inputs, plain buttons) keep native Tab/arrow behaviour.
|
|
110
|
+
if (!(event.target instanceof HTMLElement) ||
|
|
111
|
+
event.target.getAttribute("role") !== "menuitem") {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (event.key === "Tab") {
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
setOpen(false);
|
|
117
|
+
triggerRef.current?.focus();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key))
|
|
121
|
+
return;
|
|
122
|
+
const menuItems = Array.from(panelRef.current?.querySelectorAll(MENU_ITEM_SELECTOR) ?? []);
|
|
123
|
+
if (menuItems.length === 0)
|
|
124
|
+
return;
|
|
125
|
+
const current = menuItems.indexOf(document.activeElement);
|
|
126
|
+
const last = menuItems.length - 1;
|
|
127
|
+
const next = event.key === "Home"
|
|
128
|
+
? 0
|
|
129
|
+
: event.key === "End"
|
|
130
|
+
? last
|
|
131
|
+
: event.key === "ArrowDown"
|
|
132
|
+
? current >= last
|
|
133
|
+
? 0
|
|
134
|
+
: current + 1
|
|
135
|
+
: current <= 0
|
|
136
|
+
? last
|
|
137
|
+
: current - 1;
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
menuItems[next]?.focus();
|
|
140
|
+
};
|
|
141
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: triggerRef, type: "button", "aria-label": t("kumiko.page-header.actions"), "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": panelId, "data-testid": "shell-header-overflow-trigger", onClick: () => setOpen((wasOpen) => !wasOpen), className: "inline-flex size-10 shrink-0 items-center justify-center rounded-md border border-input bg-background text-foreground shadow-xs hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", children: _jsx(MoreHorizontal, { className: "size-4", "aria-hidden": "true" }) }), _jsxs("div", { ref: panelRef, id: panelId, role: "menu", "aria-label": t("kumiko.page-header.actions"), hidden: !open, onKeyDown: onPanelKeyDown, "data-testid": "shell-header-overflow", className: "absolute right-2 top-full z-30 mt-1 w-max min-w-[8rem] max-w-[calc(100vw-1rem)] rounded-md border bg-popover p-1 text-popover-foreground shadow-md", children: [items.length > 0 && (_jsx("div", { className: "flex flex-col", children: items.map((item) => (_jsxs("button", { type: "button", role: "menuitem", disabled: item.disabled === true, "data-testid": item.testId ?? `shell-header-overflow-item-${item.id}`, onClick: () => {
|
|
142
|
+
setOpen(false);
|
|
143
|
+
item.onSelect();
|
|
144
|
+
}, className: cn(headerOverflowMenuItemClass, item.variant === "danger" && "text-destructive"), children: [item.icon !== undefined && Object.hasOwn(NAV_ICONS, item.icon) && (_jsx(Icon, { name: item.icon, className: "size-4 shrink-0" })), item.label] }, item.id))) })), items.length > 0 && headerActions !== undefined && (_jsx("hr", { className: "-mx-1 my-1 border-t border-border" })), headerActions !== undefined && (_jsx("div", { "data-kumiko-layout": "header-actions",
|
|
145
|
+
// Stacks direct children only; app wrappers opt in via HeaderActionGroup instead of
|
|
146
|
+
// being restyled through a child selector that reaches into their markup.
|
|
147
|
+
className: "flex flex-col items-stretch gap-1", onClick: () => setOpen(false), children: _jsx(HeaderOverflowMenuContext.Provider, { value: true, children: headerActions }) }))] })] }));
|
|
148
|
+
}
|
|
149
|
+
function activeNavLabel(nodes, screenId, t) {
|
|
150
|
+
for (const node of nodes) {
|
|
151
|
+
if (node.screen !== undefined && lastSegment(node.screen) === screenId) {
|
|
152
|
+
return node.label.includes(".") || node.label.includes(":") ? t(node.label) : node.label;
|
|
153
|
+
}
|
|
154
|
+
const child = activeNavLabel(node.children, screenId, t);
|
|
155
|
+
if (child !== undefined)
|
|
156
|
+
return child;
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type SidebarBrandProps = {
|
|
3
|
+
/** Workspace-/App-Name (fett, erste Zeile). */
|
|
4
|
+
readonly name: string;
|
|
5
|
+
/** Zweite Zeile — Plan, Tagline, Tenant (klein, gedimmt). */
|
|
6
|
+
readonly plan?: string;
|
|
7
|
+
/** Logo in der Kachel — typisch ein Lucide-Icon. Fehlt es, steht der
|
|
8
|
+
* erste Buchstabe des Namens. */
|
|
9
|
+
readonly logo?: ReactNode;
|
|
10
|
+
/** Zeigt das ChevronsUpDown-Icon (Aufklapp-Affordance) nur wenn die App den
|
|
11
|
+
* Brand tatsächlich in ein Dropdown wrappt. Default false: ohne Dropdown
|
|
12
|
+
* ist das Chevron irreführend (suggeriert ein Menü, das nicht aufgeht). */
|
|
13
|
+
readonly collapsible?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare function SidebarBrand({ name, plan, logo, collapsible, }: SidebarBrandProps): ReactNode;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SidebarBrand — logo tile, name and subtitle for DefaultAppShell's `brand`
|
|
3
|
+
// slot. Look only, no team-switch dropdown: an app usually has one identity;
|
|
4
|
+
// apps that need switching wrap their own dropdown around it and set
|
|
5
|
+
// `collapsible`.
|
|
6
|
+
import { ChevronsUpDown } from "lucide-react";
|
|
7
|
+
export function SidebarBrand({ name, plan, logo, collapsible = false, }) {
|
|
8
|
+
return (_jsxs("div", { className: "flex items-center gap-2.5 px-2 pt-1 pb-3 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0", children: [_jsx("div", { className: "flex size-7 shrink-0 items-center justify-center rounded-md bg-sidebar-primary text-sm font-semibold text-sidebar-primary-foreground", children: logo ?? _jsx("span", { children: name.charAt(0) }) }), _jsxs("div", { className: "grid min-w-0 flex-1 text-left leading-tight group-data-[collapsible=icon]:hidden", children: [_jsx("span", { className: "truncate text-sm font-semibold text-sidebar-foreground", children: name }), plan !== undefined && _jsx("span", { className: "truncate text-xs text-sidebar-muted", children: plan })] }), collapsible && (_jsx(ChevronsUpDown, { className: "ml-auto size-4 text-sidebar-muted group-data-[collapsible=icon]:hidden" }))] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
type SlotElement = HTMLElement | null;
|
|
3
|
+
type SidebarPanelSlot = {
|
|
4
|
+
readonly element: SlotElement;
|
|
5
|
+
/** Tells the shell state whether a screen is currently claiming the space. */
|
|
6
|
+
readonly setOccupied: (occupied: boolean) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare const SidebarPanelProvider: import("react").Provider<SidebarPanelSlot | null>;
|
|
9
|
+
/** Shells only — screens use `SidebarPanel`. */
|
|
10
|
+
export declare function useSidebarPanelSlot(): SidebarPanelSlot | null;
|
|
11
|
+
export type SidebarPanelProps = {
|
|
12
|
+
readonly children: ReactNode;
|
|
13
|
+
/** Initial width in px. After the first drag the stored width wins. */
|
|
14
|
+
readonly defaultWidth?: number;
|
|
15
|
+
readonly minWidth?: number;
|
|
16
|
+
readonly maxWidth?: number;
|
|
17
|
+
/** localStorage key for the dragged width. Without a key nothing is stored —
|
|
18
|
+
* two screens with their own list should not overwrite each other's width. */
|
|
19
|
+
readonly storageKey?: string;
|
|
20
|
+
readonly className?: string;
|
|
21
|
+
/** "nav" (default) matches the shell's own sidebar chrome. "surface" reads
|
|
22
|
+
* as page content instead of navigation — for a list that carries the
|
|
23
|
+
* screen's own content colors (e.g. status-tinted rows). */
|
|
24
|
+
readonly tone?: "nav" | "surface";
|
|
25
|
+
};
|
|
26
|
+
/** Renders its children into the second sidebar column when the shell offers
|
|
27
|
+
* one. When it does not (public surface, tests, an older shell) the children
|
|
28
|
+
* land in place — the screen stays usable instead of losing its list.
|
|
29
|
+
*
|
|
30
|
+
* The width drags like in any mail client: the handle sits on the divider and
|
|
31
|
+
* the result survives a reload (with `storageKey`). */
|
|
32
|
+
export declare function SidebarPanel({ children, defaultWidth, minWidth, maxWidth, storageKey, className, tone, }: SidebarPanelProps): ReactNode;
|
|
33
|
+
/** Shell-side counterpart: holds the slot element and the occupied state. Its
|
|
34
|
+
* own hook so both shells (WorkspaceShell, DefaultAppShell) share one wiring
|
|
35
|
+
* instead of getting the same thing half right twice. */
|
|
36
|
+
export declare function useSidebarPanelHost(): {
|
|
37
|
+
readonly occupied: boolean;
|
|
38
|
+
readonly value: SidebarPanelSlot;
|
|
39
|
+
readonly ref: (node: SlotElement) => void;
|
|
40
|
+
};
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// A second sidebar column for screens that need a list next to the navigation
|
|
3
|
+
// rather than inside the content — the shadcn `sidebar-09` pattern (mail
|
|
4
|
+
// client) against the `sidebar-07` the shell otherwise runs.
|
|
5
|
+
//
|
|
6
|
+
// Why a slot and not a screen layout: the area sits outside the `SidebarInset`
|
|
7
|
+
// and therefore above the ShellHeader. A screen renders by definition *into*
|
|
8
|
+
// the content, below the header, and cannot reach that spot from there. The
|
|
9
|
+
// slot inverts the direction: the shell holds the space, the screen fills it
|
|
10
|
+
// through a portal and stays an ordinary screen otherwise.
|
|
11
|
+
//
|
|
12
|
+
// With no screen filling it the shell does not render the space at all — no
|
|
13
|
+
// empty strip on every other screen.
|
|
14
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState, } from "react";
|
|
15
|
+
import { createPortal } from "react-dom";
|
|
16
|
+
import { clamp } from "../lib/clamp.js";
|
|
17
|
+
import { cn } from "../lib/cn.js";
|
|
18
|
+
const SidebarPanelContext = createContext(null);
|
|
19
|
+
export const SidebarPanelProvider = SidebarPanelContext.Provider;
|
|
20
|
+
/** Shells only — screens use `SidebarPanel`. */
|
|
21
|
+
export function useSidebarPanelSlot() {
|
|
22
|
+
return useContext(SidebarPanelContext);
|
|
23
|
+
}
|
|
24
|
+
const DEFAULT_WIDTH = 340;
|
|
25
|
+
const DEFAULT_MIN = 260;
|
|
26
|
+
const DEFAULT_MAX = 640;
|
|
27
|
+
function readStoredWidth(key) {
|
|
28
|
+
if (key === undefined || typeof window === "undefined")
|
|
29
|
+
return null;
|
|
30
|
+
const raw = window.localStorage.getItem(key);
|
|
31
|
+
if (raw === null)
|
|
32
|
+
return null;
|
|
33
|
+
const parsed = Number.parseInt(raw, 10);
|
|
34
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
35
|
+
}
|
|
36
|
+
/** Renders its children into the second sidebar column when the shell offers
|
|
37
|
+
* one. When it does not (public surface, tests, an older shell) the children
|
|
38
|
+
* land in place — the screen stays usable instead of losing its list.
|
|
39
|
+
*
|
|
40
|
+
* The width drags like in any mail client: the handle sits on the divider and
|
|
41
|
+
* the result survives a reload (with `storageKey`). */
|
|
42
|
+
export function SidebarPanel({ children, defaultWidth = DEFAULT_WIDTH, minWidth = DEFAULT_MIN, maxWidth = DEFAULT_MAX, storageKey, className, tone = "nav", }) {
|
|
43
|
+
const slot = useSidebarPanelSlot();
|
|
44
|
+
const setOccupied = slot?.setOccupied;
|
|
45
|
+
const [width, setWidth] = useState(() => clamp(readStoredWidth(storageKey) ?? defaultWidth, minWidth, maxWidth));
|
|
46
|
+
const dragging = useRef(false);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (setOccupied === undefined)
|
|
49
|
+
return;
|
|
50
|
+
setOccupied(true);
|
|
51
|
+
return () => setOccupied(false);
|
|
52
|
+
}, [setOccupied]);
|
|
53
|
+
// Listeners on window, not on the handle: a fast drag leaves the 4px strip
|
|
54
|
+
// between two frames, and a handler on the element would lose the drag there.
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const onMove = (event) => {
|
|
57
|
+
if (!dragging.current)
|
|
58
|
+
return;
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
setWidth(clamp(event.clientX, minWidth, maxWidth));
|
|
61
|
+
};
|
|
62
|
+
const onUp = () => {
|
|
63
|
+
if (!dragging.current)
|
|
64
|
+
return;
|
|
65
|
+
dragging.current = false;
|
|
66
|
+
document.body.style.removeProperty("cursor");
|
|
67
|
+
document.body.style.removeProperty("user-select");
|
|
68
|
+
};
|
|
69
|
+
window.addEventListener("pointermove", onMove);
|
|
70
|
+
window.addEventListener("pointerup", onUp);
|
|
71
|
+
return () => {
|
|
72
|
+
window.removeEventListener("pointermove", onMove);
|
|
73
|
+
window.removeEventListener("pointerup", onUp);
|
|
74
|
+
};
|
|
75
|
+
}, [minWidth, maxWidth]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (storageKey === undefined || typeof window === "undefined")
|
|
78
|
+
return;
|
|
79
|
+
window.localStorage.setItem(storageKey, String(width));
|
|
80
|
+
}, [storageKey, width]);
|
|
81
|
+
const startDrag = useCallback((event) => {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
dragging.current = true;
|
|
84
|
+
// Set globally while dragging: otherwise the cursor flickers as soon as the
|
|
85
|
+
// pointer sits over the list instead of the handle, and a drag selects the
|
|
86
|
+
// row text on the way.
|
|
87
|
+
document.body.style.setProperty("cursor", "col-resize");
|
|
88
|
+
document.body.style.setProperty("user-select", "none");
|
|
89
|
+
}, []);
|
|
90
|
+
const content = (_jsxs("div", { "data-kumiko-layout": "sidebar-panel-body", className: cn("relative flex h-full shrink-0 flex-col border-r", tone === "surface" ? "border-border bg-muted" : "border-sidebar-border bg-sidebar", className), style: { width: `${width}px` }, children: [children, _jsx("div", { "data-kumiko-layout": "sidebar-panel-handle", onPointerDown: startDrag, className: cn("absolute inset-y-0 right-0 w-1 cursor-col-resize bg-transparent transition-colors", tone === "surface" ? "hover:bg-border" : "hover:bg-sidebar-border") })] }));
|
|
91
|
+
if (slot?.element == null) {
|
|
92
|
+
return content;
|
|
93
|
+
}
|
|
94
|
+
return createPortal(content, slot.element);
|
|
95
|
+
}
|
|
96
|
+
/** Shell-side counterpart: holds the slot element and the occupied state. Its
|
|
97
|
+
* own hook so both shells (WorkspaceShell, DefaultAppShell) share one wiring
|
|
98
|
+
* instead of getting the same thing half right twice. */
|
|
99
|
+
export function useSidebarPanelHost() {
|
|
100
|
+
const [element, setElement] = useState(null);
|
|
101
|
+
const [occupied, setOccupied] = useState(false);
|
|
102
|
+
return {
|
|
103
|
+
occupied,
|
|
104
|
+
value: { element, setOccupied },
|
|
105
|
+
ref: setElement,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type SidebarUserProps = {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly email?: string;
|
|
5
|
+
/** Avatar-URL. Fehlt sie, stehen die Initialen aus dem Namen. */
|
|
6
|
+
readonly avatar?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function SidebarUser({ name, email, avatar }: SidebarUserProps): ReactNode;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SidebarUser — der NavUser-Footer aus shadcns sidebar-07: Avatar + Name +
|
|
3
|
+
// Email + ChevronsUpDown. App-Author reicht ihn als `sidebarFooter` an
|
|
4
|
+
// DefaultAppShell. Reiner Look (kein Account-Dropdown) — wer ein User-Menü
|
|
5
|
+
// will, wrappt ihn in einen DropdownMenu.
|
|
6
|
+
import { ChevronsUpDown } from "lucide-react";
|
|
7
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar.js";
|
|
8
|
+
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar.js";
|
|
9
|
+
function initials(name) {
|
|
10
|
+
const parts = name.trim().split(/\s+/);
|
|
11
|
+
const first = parts[0]?.charAt(0) ?? "";
|
|
12
|
+
const last = parts.length > 1 ? (parts[parts.length - 1]?.charAt(0) ?? "") : "";
|
|
13
|
+
return (first + last).toUpperCase() || name.slice(0, 2).toUpperCase();
|
|
14
|
+
}
|
|
15
|
+
export function SidebarUser({ name, email, avatar }) {
|
|
16
|
+
return (_jsx(SidebarMenu, { children: _jsx(SidebarMenuItem, { children: _jsxs(SidebarMenuButton, { size: "lg", className: "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground", children: [_jsxs(Avatar, { className: "size-7 bg-sidebar-accent", children: [avatar !== undefined && _jsx(AvatarImage, { src: avatar, alt: name }), _jsx(AvatarFallback, { className: "bg-sidebar-accent text-xs font-medium text-sidebar-accent-foreground", children: initials(name) })] }), _jsxs("div", { className: "grid flex-1 text-left leading-tight", children: [_jsx("span", { className: "truncate font-medium text-sidebar-foreground", children: name }), email !== undefined && (_jsx("span", { className: "truncate text-xs text-sidebar-muted", children: email }))] }), _jsx(ChevronsUpDown, { className: "ml-auto size-4 text-sidebar-muted" })] }) }) }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type SidebarProps = {
|
|
3
|
+
/** Header-Bereich oben — typisch Workspace-Avatar + Name + Badge.
|
|
4
|
+
* Lebt VOR dem scroll-area, bleibt sichtbar wenn das nav scrollt. */
|
|
5
|
+
readonly header?: ReactNode;
|
|
6
|
+
/** Action-Cluster zwischen Header und Nav — typisch "+ Neu" Button
|
|
7
|
+
* und Search-Icon. Linear-Pattern: prominent, nicht im scroll-area. */
|
|
8
|
+
readonly actions?: ReactNode;
|
|
9
|
+
/** NavTree + freie Sections (z.B. "Your teams" Header + Sub-Items).
|
|
10
|
+
* Scrollt eigenständig wenn der Inhalt zu lang wird. */
|
|
11
|
+
readonly children: ReactNode;
|
|
12
|
+
/** Footer-Bereich unten — typisch Invite-Link, Help, Plan-Banner.
|
|
13
|
+
* Klebt am unteren Rand via mt-auto. */
|
|
14
|
+
readonly footer?: ReactNode;
|
|
15
|
+
readonly testId?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function Sidebar({ header, actions, children, footer, testId }: SidebarProps): ReactNode;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function Sidebar({ header, actions, children, footer, testId }) {
|
|
3
|
+
// Linear-Pattern: 4 vertikale Bereiche
|
|
4
|
+
// 1. Header (Workspace-Identity)
|
|
5
|
+
// 2. Actions (Quick-Buttons, nicht-scrollend)
|
|
6
|
+
// 3. Nav-Scroll-Area (NavTree, scrollt wenn nötig)
|
|
7
|
+
// 4. Footer (klebt unten via mt-auto)
|
|
8
|
+
// bg-muted/40 + border-r für visuelle Distinction zur Main-Area;
|
|
9
|
+
// tighter padding (p-3) und kompakte gap-0.5 zwischen Items.
|
|
10
|
+
return (_jsxs("aside", { "data-testid": testId, "data-kumiko-layout": "sidebar", className: "hidden md:flex w-60 flex-shrink-0 bg-transparent flex-col text-sm", children: [header !== undefined && (_jsx("div", { "data-kumiko-layout": "sidebar-header", className: "h-12 flex items-center px-3 border-b border-border/50", children: header })), actions !== undefined && (_jsx("div", { "data-kumiko-layout": "sidebar-actions", className: "px-3 py-2 border-b border-border/50 flex flex-row items-center gap-1", children: actions })), _jsx("div", { "data-kumiko-layout": "sidebar-nav", className: "px-3 py-2 flex flex-col gap-0.5 overflow-auto flex-1", children: children }), footer !== undefined && (_jsx("div", { "data-kumiko-layout": "sidebar-footer", className: "px-3 py-2 border-t mt-auto flex flex-col gap-1 text-xs text-muted-foreground", children: footer }))] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TargetRef } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
type DispatchListener = (target: TargetRef) => void;
|
|
3
|
+
export declare function dispatchTarget(target: TargetRef): void;
|
|
4
|
+
/** Test-Hook: Exklusiver Spy. Returnt cleanup. */
|
|
5
|
+
export declare function setDispatchListener(fn: DispatchListener): () => void;
|
|
6
|
+
/** Production-Subscriber registrieren. Returnt unsubscribe. Läuft nur
|
|
7
|
+
* wenn kein Test-Listener aktiv (Test-Isolation). */
|
|
8
|
+
export declare function subscribeTargetDispatches(fn: DispatchListener): () => void;
|
|
9
|
+
/** V.1.4b: empfohlener Production-Dispatch. Schreibt target in
|
|
10
|
+
* nav.searchParams (URL-State, F5-fähig) UND ruft dispatchTarget für
|
|
11
|
+
* Test-Listener + legacy-Subscribers. */
|
|
12
|
+
export declare function useDispatchTarget(): (target: TargetRef) => void;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// TargetResolver — V.1.2: Multi-Listener-TargetDispatch mit Test-Hook.
|
|
2
|
+
// V.1.4b: URL-State-Bridge via useDispatchTarget-Hook. Production schreibt
|
|
3
|
+
// target in nav.searchParams (F5-recovery); Subscribe-Stream bleibt für
|
|
4
|
+
// Test-Hooks (setDispatchListener) und Apps die kein NavProvider haben.
|
|
5
|
+
//
|
|
6
|
+
// Dispatch-Priority (V.1.2):
|
|
7
|
+
// 1. Test-Listener (setDispatchListener) — exklusiv, kein Production-
|
|
8
|
+
// Subscriber läuft während Tests (Test-Isolation).
|
|
9
|
+
// 2. Production-Subscriber (subscribeTargetDispatches) — alle
|
|
10
|
+
// registrierten Production-Subscriber.
|
|
11
|
+
// 3. Kein Test-Listener + keine Subscriber → console.debug fallback
|
|
12
|
+
// (damit unhandled Klicks sichtbar bleiben).
|
|
13
|
+
//
|
|
14
|
+
// **useDispatchTarget (V.1.4b)** ist der empfohlene Production-Pfad.
|
|
15
|
+
// NavTree ruft den Hook im Click-Handler eines target-Knotens — er schreibt
|
|
16
|
+
// die URL via nav.setSearchParams UND ruft den globalen dispatchTarget für
|
|
17
|
+
// Test-Listener-Kompatibilität.
|
|
18
|
+
import { useCallback } from "react";
|
|
19
|
+
import { useNav } from "../../index.js";
|
|
20
|
+
import { serializeTarget } from "./target-url.js";
|
|
21
|
+
let testListener;
|
|
22
|
+
const subscribers = new Set();
|
|
23
|
+
export function dispatchTarget(target) {
|
|
24
|
+
if (testListener !== undefined) {
|
|
25
|
+
testListener(target);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (subscribers.size > 0) {
|
|
29
|
+
for (const fn of subscribers) {
|
|
30
|
+
fn(target);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// biome-ignore lint/suspicious/noConsole: fallback wenn kein Subscriber registered
|
|
35
|
+
console.debug("[NavTree] target dispatched (unhandled)", target);
|
|
36
|
+
}
|
|
37
|
+
/** Test-Hook: Exklusiver Spy. Returnt cleanup. */
|
|
38
|
+
export function setDispatchListener(fn) {
|
|
39
|
+
testListener = fn;
|
|
40
|
+
return () => {
|
|
41
|
+
testListener = undefined;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Production-Subscriber registrieren. Returnt unsubscribe. Läuft nur
|
|
45
|
+
* wenn kein Test-Listener aktiv (Test-Isolation). */
|
|
46
|
+
export function subscribeTargetDispatches(fn) {
|
|
47
|
+
subscribers.add(fn);
|
|
48
|
+
return () => {
|
|
49
|
+
subscribers.delete(fn);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** V.1.4b: empfohlener Production-Dispatch. Schreibt target in
|
|
53
|
+
* nav.searchParams (URL-State, F5-fähig) UND ruft dispatchTarget für
|
|
54
|
+
* Test-Listener + legacy-Subscribers. */
|
|
55
|
+
export function useDispatchTarget() {
|
|
56
|
+
const nav = useNav();
|
|
57
|
+
return useCallback((target) => {
|
|
58
|
+
nav.setSearchParams(serializeTarget(target, nav.searchParams));
|
|
59
|
+
dispatchTarget(target);
|
|
60
|
+
}, [nav]);
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TargetRef } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
/** Build a search-params update for `setSearchParams`. Clears all
|
|
3
|
+
* existing `a_*` keys plus `t` (so wechsel target nicht alte args
|
|
4
|
+
* liegen lässt). Returns null-value entries to clear, plus the new
|
|
5
|
+
* target entries. */
|
|
6
|
+
export declare function serializeTarget(target: TargetRef, currentParams: Readonly<Record<string, string>>): Readonly<Record<string, string | null>>;
|
|
7
|
+
/** Inverse — parse the current search-params back into a TargetRef.
|
|
8
|
+
* Returns undefined wenn der `t`-Key fehlt (kein active target). */
|
|
9
|
+
export declare function parseTargetFromSearchParams(params: Readonly<Record<string, string>>): TargetRef | undefined;
|
|
10
|
+
/** Returns null-valued updates that clear target + all args. Used vom
|
|
11
|
+
* Close-Button + onUnmount. */
|
|
12
|
+
export declare function clearTargetSearchParams(currentParams: Readonly<Record<string, string>>): Readonly<Record<string, string | null>>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @runtime client
|
|
2
|
+
// URL-Bridge für Visual-Tree-Targets. Serialisiert TargetRef in
|
|
3
|
+
// Search-Params + parsed zurück.
|
|
4
|
+
//
|
|
5
|
+
// **URL-Shape**:
|
|
6
|
+
// ?t=<featureId>:<action>&a_<argKey1>=<value1>&a_<argKey2>=<value2>...
|
|
7
|
+
//
|
|
8
|
+
// Beispiel: text-content edit "imprint/de"
|
|
9
|
+
// ?t=text-content:edit&a_slug=imprint&a_lang=de
|
|
10
|
+
//
|
|
11
|
+
// **Warum prefix `a_`**: vermeidet Naming-Konflikt mit anderen Query-
|
|
12
|
+
// Params (Pagination, Sort, Filter). Plus klare Trennung target-meta
|
|
13
|
+
// (`t`) vs. action-args (`a_*`).
|
|
14
|
+
//
|
|
15
|
+
// **Warum nicht JSON-encoded**: URL bleibt lesbar + bookmark-fähig.
|
|
16
|
+
// JSON-base64 wäre robust für arbitrary Shapes aber unleserlich. arg-
|
|
17
|
+
// values sind heute nur primitive strings (text-content: slug, lang) —
|
|
18
|
+
// V.1.5 kann auf JSON wechseln wenn nested-args echten Bedarf zeigen.
|
|
19
|
+
const TARGET_PARAM = "t";
|
|
20
|
+
const ARG_PREFIX = "a_";
|
|
21
|
+
/** Build a search-params update for `setSearchParams`. Clears all
|
|
22
|
+
* existing `a_*` keys plus `t` (so wechsel target nicht alte args
|
|
23
|
+
* liegen lässt). Returns null-value entries to clear, plus the new
|
|
24
|
+
* target entries. */
|
|
25
|
+
export function serializeTarget(target, currentParams) {
|
|
26
|
+
const updates = {};
|
|
27
|
+
// Clear all previous arg-keys (vermeidet stale args bei target-switch).
|
|
28
|
+
for (const key of Object.keys(currentParams)) {
|
|
29
|
+
if (key.startsWith(ARG_PREFIX))
|
|
30
|
+
updates[key] = null;
|
|
31
|
+
}
|
|
32
|
+
updates[TARGET_PARAM] = `${target.featureId}:${target.action}`;
|
|
33
|
+
if (target.args !== undefined) {
|
|
34
|
+
for (const [k, v] of Object.entries(target.args)) {
|
|
35
|
+
// V.1.4b: nur string-args. Numbers/booleans werden via String()
|
|
36
|
+
// koerziert; nested objects/arrays werden NICHT supported (würde
|
|
37
|
+
// JSON-encoded URL brauchen, siehe Header-Comment).
|
|
38
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
39
|
+
updates[`${ARG_PREFIX}${k}`] = String(v);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return updates;
|
|
44
|
+
}
|
|
45
|
+
/** Inverse — parse the current search-params back into a TargetRef.
|
|
46
|
+
* Returns undefined wenn der `t`-Key fehlt (kein active target). */
|
|
47
|
+
export function parseTargetFromSearchParams(params) {
|
|
48
|
+
const t = params[TARGET_PARAM];
|
|
49
|
+
if (t === undefined || t === "")
|
|
50
|
+
return undefined;
|
|
51
|
+
const sepIdx = t.indexOf(":");
|
|
52
|
+
if (sepIdx < 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
const featureId = t.slice(0, sepIdx);
|
|
55
|
+
const action = t.slice(sepIdx + 1);
|
|
56
|
+
if (featureId === "" || action === "")
|
|
57
|
+
return undefined;
|
|
58
|
+
const args = {};
|
|
59
|
+
let hasArgs = false;
|
|
60
|
+
for (const [key, value] of Object.entries(params)) {
|
|
61
|
+
if (key.startsWith(ARG_PREFIX)) {
|
|
62
|
+
args[key.slice(ARG_PREFIX.length)] = value;
|
|
63
|
+
hasArgs = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return hasArgs ? { featureId, action, args } : { featureId, action };
|
|
67
|
+
}
|
|
68
|
+
/** Returns null-valued updates that clear target + all args. Used vom
|
|
69
|
+
* Close-Button + onUnmount. */
|
|
70
|
+
export function clearTargetSearchParams(currentParams) {
|
|
71
|
+
const updates = { [TARGET_PARAM]: null };
|
|
72
|
+
for (const key of Object.keys(currentParams)) {
|
|
73
|
+
if (key.startsWith(ARG_PREFIX))
|
|
74
|
+
updates[key] = null;
|
|
75
|
+
}
|
|
76
|
+
return updates;
|
|
77
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ThemePreference } from "../../index.ts";
|
|
2
|
+
export type ThemeToggleLabelOptions = {
|
|
3
|
+
/** Title while the next step is the light theme. */
|
|
4
|
+
readonly titleInDark?: string | undefined;
|
|
5
|
+
/** Title while the next step is the dark theme. */
|
|
6
|
+
readonly titleInLight?: string | undefined;
|
|
7
|
+
/** Title while the next step is the automatic theme. */
|
|
8
|
+
readonly titleForAuto?: string | undefined;
|
|
9
|
+
};
|
|
10
|
+
export declare function nextThemePreference(current: ThemePreference): ThemePreference;
|
|
11
|
+
export declare function useThemeToggleLabel({ titleInDark, titleInLight, titleForAuto, }: ThemeToggleLabelOptions): {
|
|
12
|
+
readonly nextPreference: ThemePreference;
|
|
13
|
+
readonly title: string;
|
|
14
|
+
readonly cyclePreference: () => void;
|
|
15
|
+
};
|