@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,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Update-Awareness — default an, kein Flag. Ein offener Tab erfährt sonst
|
|
3
|
+
// nie von einem neuen Deploy (SPA bleibt geladen, index.html wird nicht neu
|
|
4
|
+
// geholt → "muss hart reloaden"). Statt Service-Worker (der ist die *Ursache*
|
|
5
|
+
// von "hängt auf alter Version") pollt diese Komponente die statische
|
|
6
|
+
// build-info.json beim Tab-Fokus und zeigt ein Reload-Banner bei Drift.
|
|
7
|
+
//
|
|
8
|
+
// Versionsquelle ist build-info.json (Hash über die Asset-URLs, vom Prod-Build
|
|
9
|
+
// geschrieben) — NICHT /api/version (live leer/404, Env-/Dockerfile-Falle).
|
|
10
|
+
// Banner-Strings über kumikoDefaultTranslations (kumiko.version.*, de/en).
|
|
11
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
12
|
+
import { useEffect, useState } from "react";
|
|
13
|
+
import { useTranslation } from "../../index.js";
|
|
14
|
+
import { cn } from "../lib/cn.js";
|
|
15
|
+
export function readLoadedBuild() {
|
|
16
|
+
if (typeof document === "undefined")
|
|
17
|
+
return undefined;
|
|
18
|
+
const meta = document.querySelector('meta[name="kumiko-build"]');
|
|
19
|
+
const id = meta?.getAttribute("content");
|
|
20
|
+
if (!id)
|
|
21
|
+
return undefined;
|
|
22
|
+
return { id, builtAt: meta?.getAttribute("data-built-at") ?? "" };
|
|
23
|
+
}
|
|
24
|
+
// Korrektheitsgrenze der Update-Erkennung: ein Banner NUR bei echtem
|
|
25
|
+
// ID-Drift. Kein geladener Stand (Dev/altes Bundle) oder kein Server-Stand
|
|
26
|
+
// (Fetch-Fehler/kaputtes JSON → null) → nie ein Fake-Banner.
|
|
27
|
+
export function shouldShowUpdate(loadedId, server) {
|
|
28
|
+
if (!loadedId || !server)
|
|
29
|
+
return false;
|
|
30
|
+
return server.id !== loadedId;
|
|
31
|
+
}
|
|
32
|
+
export function isKumikoBuild(v) {
|
|
33
|
+
return (typeof v === "object" && v !== null && "id" in v && typeof v.id === "string" && v.id !== "");
|
|
34
|
+
}
|
|
35
|
+
async function fetchServerBuild() {
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch("/build-info.json", { cache: "no-store" });
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
return null;
|
|
40
|
+
const raw = await res.json();
|
|
41
|
+
if (!isKumikoBuild(raw))
|
|
42
|
+
return null;
|
|
43
|
+
const builtAt = propertyOf(raw, "builtAt");
|
|
44
|
+
return { id: raw.id, builtAt: typeof builtAt === "string" ? builtAt : "" };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Netzwerkfehler / kaputtes JSON → kein Banner. Nie ein Fake-Update zeigen.
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function UpdateChecker() {
|
|
52
|
+
const t = useTranslation();
|
|
53
|
+
const loaded = readLoadedBuild();
|
|
54
|
+
const [hasUpdate, setHasUpdate] = useState(false);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const loadedId = loaded?.id;
|
|
57
|
+
if (!loadedId)
|
|
58
|
+
return; // Dev / altes Bundle → keine Awareness.
|
|
59
|
+
let cancelled = false;
|
|
60
|
+
const checking = { current: false };
|
|
61
|
+
const check = async () => {
|
|
62
|
+
if (document.visibilityState !== "visible")
|
|
63
|
+
return;
|
|
64
|
+
if (checking.current)
|
|
65
|
+
return; // visibilitychange + focus fire together on tab-return
|
|
66
|
+
checking.current = true;
|
|
67
|
+
try {
|
|
68
|
+
const server = await fetchServerBuild();
|
|
69
|
+
if (!cancelled && shouldShowUpdate(loadedId, server))
|
|
70
|
+
setHasUpdate(true);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
checking.current = false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
document.addEventListener("visibilitychange", check);
|
|
77
|
+
window.addEventListener("focus", check);
|
|
78
|
+
void check();
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true;
|
|
81
|
+
document.removeEventListener("visibilitychange", check);
|
|
82
|
+
window.removeEventListener("focus", check);
|
|
83
|
+
};
|
|
84
|
+
}, [loaded?.id]);
|
|
85
|
+
if (!hasUpdate)
|
|
86
|
+
return null;
|
|
87
|
+
return (_jsxs("div", { role: "status", className: cn("fixed inset-x-0 top-0 z-[200] flex items-center justify-center gap-3", "border-b bg-background px-4 py-2 text-sm text-foreground shadow-md"), children: [_jsx("span", { children: t("kumiko.version.update-available") }), _jsx("button", { type: "button", onClick: () => window.location.reload(), className: cn("rounded-md bg-primary px-3 py-1 text-xs font-medium text-primary-foreground", "hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-ring"), children: t("kumiko.actions.reload") })] }));
|
|
88
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
type AiTextAction = "correct" | "translate" | "rewrite";
|
|
3
|
+
interface AiTextFieldBaseProps {
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly value: string;
|
|
8
|
+
readonly onChange: (v: string) => void;
|
|
9
|
+
readonly required?: boolean;
|
|
10
|
+
readonly disabled?: boolean;
|
|
11
|
+
readonly placeholder?: string;
|
|
12
|
+
readonly testId?: string;
|
|
13
|
+
readonly hideLabel?: boolean;
|
|
14
|
+
/** Ghost-text completion on/off. Default true. */
|
|
15
|
+
readonly completion?: boolean;
|
|
16
|
+
/** Debounce for ghost-text requests, ms. Default 500 — kept low in
|
|
17
|
+
* tests, tuned for real typing in production. */
|
|
18
|
+
readonly completionDebounceMs?: number;
|
|
19
|
+
/** Toolbar actions to expose. Default all three. */
|
|
20
|
+
readonly actions?: readonly AiTextAction[];
|
|
21
|
+
/** Target languages the translate action offers. First is preselected.
|
|
22
|
+
* Default `["en", "de", "fr", "es"]`. */
|
|
23
|
+
readonly translateLanguages?: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
export interface AiTextFieldProps extends AiTextFieldBaseProps {
|
|
26
|
+
}
|
|
27
|
+
export interface AiTextAreaProps extends AiTextFieldBaseProps {
|
|
28
|
+
readonly rows?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Single-line AI text field — ghost-text completion + correct/translate/
|
|
31
|
+
* rewrite toolbar. Degrades to a plain text field when `ai-text` isn't
|
|
32
|
+
* mounted server-side. */
|
|
33
|
+
export declare function AiTextField(props: AiTextFieldProps): ReactNode;
|
|
34
|
+
/** Multi-line variant of {@link AiTextField}. */
|
|
35
|
+
export declare function AiTextArea(props: AiTextAreaProps): ReactNode;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// AiTextField / AiTextArea — Drop-in replacement for TextField/TextareaField
|
|
3
|
+
// with AI-augmented actions (ai-text-primitive plan doc, Phase 3).
|
|
4
|
+
//
|
|
5
|
+
// Built on raw <input>/<textarea> (not the generic Input primitive) because
|
|
6
|
+
// ghost-text needs a keydown handler (Tab/Esc) and an overlay that the
|
|
7
|
+
// discriminated-union Input contract doesn't expose. Same pattern as
|
|
8
|
+
// MoneyInput — a hand-built control wrapped in Field.
|
|
9
|
+
//
|
|
10
|
+
// Graceful degradation: `useAiTextAction`/`useCompletion` surface
|
|
11
|
+
// "unavailable" when the server feature isn't mounted (`feature_disabled`)
|
|
12
|
+
// — the toolbar hides and the field behaves like a plain text field, no
|
|
13
|
+
// error shown to the end-user.
|
|
14
|
+
//
|
|
15
|
+
// Ghost-text simplification: the overlay only ever appends after the
|
|
16
|
+
// current value (no mid-text ghost) — matches the server prompt design
|
|
17
|
+
// (ai-text's `complete` mode only ever continues forward from the given
|
|
18
|
+
// text). Two-layer overlay trick: an aria-hidden div behind the real
|
|
19
|
+
// input renders `value` (invisible, same font/box so it reserves space)
|
|
20
|
+
// followed by the suggestion in muted color; the real input sits on top
|
|
21
|
+
// with a transparent background so the suggestion shows through past the
|
|
22
|
+
// typed text. Both elements MUST share identical typography/box classes
|
|
23
|
+
// or the suggestion won't align with the caret.
|
|
24
|
+
import { Check, Languages, Wand2 } from "lucide-react";
|
|
25
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
26
|
+
import { useAiTextAction, useCompletion, usePrimitives, useTranslation } from "../../index.js";
|
|
27
|
+
import { cn } from "../lib/cn.js";
|
|
28
|
+
import { ModeSwitch } from "./mode-switch.js";
|
|
29
|
+
const DEFAULT_LANGUAGES = ["en", "de", "fr", "es"];
|
|
30
|
+
const DEFAULT_ACTIONS = ["correct", "translate", "rewrite"];
|
|
31
|
+
const REWRITE_STYLES = ["formal", "casual", "concise", "expand"];
|
|
32
|
+
const sharedTextClass = "w-full rounded-md border border-input px-3 py-2 text-sm shadow-sm leading-6 " +
|
|
33
|
+
"placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 " +
|
|
34
|
+
"focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50";
|
|
35
|
+
// Overlay and real control must share identical wrap/overflow behavior or the
|
|
36
|
+
// ghost suggestion drifts from the caret: <input> never wraps and scrolls
|
|
37
|
+
// horizontally, <textarea> soft-wraps and scrolls vertically.
|
|
38
|
+
const singleLineWrapClass = "whitespace-pre overflow-hidden";
|
|
39
|
+
const multilineWrapClass = "whitespace-pre-wrap break-words overflow-hidden";
|
|
40
|
+
function AiTextCore({ multiline, label, id, name, value, onChange, required, disabled, placeholder, testId, hideLabel, rows, completion = true, completionDebounceMs = 500, actions = DEFAULT_ACTIONS, translateLanguages = DEFAULT_LANGUAGES, }) {
|
|
41
|
+
const { Field, Button, Dialog, Text } = usePrimitives();
|
|
42
|
+
const t = useTranslation();
|
|
43
|
+
const { suggestion, state: completionState, requestCompletion, clear: clearCompletion, } = useCompletion(completionDebounceMs);
|
|
44
|
+
const { run: runAction, state: actionState, result: actionResult, reset: resetAction, } = useAiTextAction();
|
|
45
|
+
const [dialogMode, setDialogMode] = useState(null);
|
|
46
|
+
const [targetLanguage, setTargetLanguage] = useState(translateLanguages[0] ?? "en");
|
|
47
|
+
const [rewriteStyle, setRewriteStyle] = useState("concise");
|
|
48
|
+
const overlayRef = useRef(null);
|
|
49
|
+
const controlRef = useRef(null);
|
|
50
|
+
const unavailable = completionState === "unavailable" || actionState === "unavailable";
|
|
51
|
+
const capExceeded = completionState === "cap-exceeded" || actionState === "cap-exceeded";
|
|
52
|
+
const showToolbar = !unavailable && actions.length > 0;
|
|
53
|
+
const showGhost = completion && !unavailable && suggestion !== null && suggestion.length > 0;
|
|
54
|
+
// ponytail: a manually `resize-y`-shrunk textarea can end up a few px
|
|
55
|
+
// shorter than the overlay div (which has no resize handle), so the
|
|
56
|
+
// synced scrollTop clamps slightly early — sub-line drift, invisible at
|
|
57
|
+
// normal line-height. Upgrade if it ever becomes visually noticeable.
|
|
58
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: value/suggestion are trigger-only, effect reads refs not props
|
|
59
|
+
useLayoutEffect(() => {
|
|
60
|
+
if (overlayRef.current === null || controlRef.current === null)
|
|
61
|
+
return;
|
|
62
|
+
overlayRef.current.scrollLeft = controlRef.current.scrollLeft;
|
|
63
|
+
overlayRef.current.scrollTop = controlRef.current.scrollTop;
|
|
64
|
+
}, [value, suggestion]);
|
|
65
|
+
function handleChange(next) {
|
|
66
|
+
onChange(next);
|
|
67
|
+
if (completion && !unavailable) {
|
|
68
|
+
requestCompletion(next);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function handleKeyDown(e) {
|
|
72
|
+
if (suggestion === null)
|
|
73
|
+
return;
|
|
74
|
+
if (e.key === "Tab" && e.currentTarget.selectionStart === value.length) {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
onChange(value + suggestion);
|
|
77
|
+
clearCompletion();
|
|
78
|
+
}
|
|
79
|
+
else if (e.key === "Escape") {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
clearCompletion();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function openDialog(mode) {
|
|
85
|
+
resetAction();
|
|
86
|
+
setDialogMode(mode);
|
|
87
|
+
if (mode === "correct") {
|
|
88
|
+
void runAction({ mode: "correct", text: value });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function closeDialog() {
|
|
92
|
+
setDialogMode(null);
|
|
93
|
+
resetAction();
|
|
94
|
+
}
|
|
95
|
+
function runConfiguredAction() {
|
|
96
|
+
if (dialogMode === "translate") {
|
|
97
|
+
void runAction({ mode: "translate", text: value, targetLanguage });
|
|
98
|
+
}
|
|
99
|
+
else if (dialogMode === "rewrite") {
|
|
100
|
+
void runAction({ mode: "rewrite", text: value, style: rewriteStyle });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function applyResult() {
|
|
104
|
+
if (actionResult?.type === "text")
|
|
105
|
+
onChange(actionResult.text);
|
|
106
|
+
}
|
|
107
|
+
const needsConfig = (dialogMode === "translate" || dialogMode === "rewrite") &&
|
|
108
|
+
actionState !== "loading" &&
|
|
109
|
+
actionState !== "success";
|
|
110
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: [_jsxs("div", { className: "relative w-full", children: [showGhost && (_jsxs("div", { ref: overlayRef, "aria-hidden": "true", className: cn(sharedTextClass, multiline ? multilineWrapClass : singleLineWrapClass, "pointer-events-none absolute inset-0 select-none border-transparent shadow-none"), children: [_jsx("span", { className: "invisible", children: value }), _jsx("span", { className: "text-muted-foreground", children: suggestion })] })), multiline ? (_jsx("textarea", { ref: (el) => {
|
|
111
|
+
controlRef.current = el;
|
|
112
|
+
}, id: id, name: name, value: value, required: required, disabled: disabled === true || unavailable, placeholder: placeholder, rows: rows ?? 4, onChange: (e) => handleChange(e.target.value), onKeyDown: handleKeyDown, onBlur: clearCompletion, onScroll: (e) => {
|
|
113
|
+
if (overlayRef.current)
|
|
114
|
+
overlayRef.current.scrollTop = e.currentTarget.scrollTop;
|
|
115
|
+
}, className: cn(sharedTextClass, multilineWrapClass, "relative resize-y bg-transparent"), "data-testid": testId !== undefined ? `${testId}-input` : undefined })) : (_jsx("input", { ref: (el) => {
|
|
116
|
+
controlRef.current = el;
|
|
117
|
+
}, id: id, name: name, type: "text", value: value, required: required, disabled: disabled === true || unavailable, placeholder: placeholder, onChange: (e) => handleChange(e.target.value), onKeyDown: handleKeyDown, onBlur: clearCompletion, onScroll: (e) => {
|
|
118
|
+
if (overlayRef.current)
|
|
119
|
+
overlayRef.current.scrollLeft = e.currentTarget.scrollLeft;
|
|
120
|
+
}, className: cn(sharedTextClass, singleLineWrapClass, "relative bg-transparent"), "data-testid": testId !== undefined ? `${testId}-input` : undefined }))] }), showGhost && _jsx(Text, { variant: "muted", children: t("kumiko.aiText.acceptHint") }), capExceeded && _jsx(Text, { variant: "muted", children: t("kumiko.aiText.capExceeded") }), showToolbar && (_jsxs("div", { className: "mt-1 flex gap-1", children: [actions.includes("correct") && (_jsx(Button, { variant: "secondary", size: "sm", disabled: value.length === 0, ariaLabel: t("kumiko.aiText.correct"), onClick: () => openDialog("correct"), children: _jsx(Check, { className: "size-3.5", "aria-hidden": "true" }) })), actions.includes("translate") && (_jsx(Button, { variant: "secondary", size: "sm", disabled: value.length === 0, ariaLabel: t("kumiko.aiText.translate"), onClick: () => openDialog("translate"), children: _jsx(Languages, { className: "size-3.5", "aria-hidden": "true" }) })), actions.includes("rewrite") && (_jsx(Button, { variant: "secondary", size: "sm", disabled: value.length === 0, ariaLabel: t("kumiko.aiText.rewrite"), onClick: () => openDialog("rewrite"), children: _jsx(Wand2, { className: "size-3.5", "aria-hidden": "true" }) }))] }))] }), _jsxs(Dialog, { open: dialogMode !== null, onOpenChange: (open) => {
|
|
121
|
+
if (!open)
|
|
122
|
+
closeDialog();
|
|
123
|
+
}, title: dialogMode !== null ? t(`kumiko.aiText.${dialogMode}`) : "", onConfirm: applyResult, testId: testId !== undefined ? `${testId}-dialog` : undefined, children: [dialogMode === "translate" && needsConfig && (_jsxs("div", { className: "mb-3 flex flex-col gap-2", children: [_jsx(ModeSwitch, { value: targetLanguage, options: translateLanguages.map((l) => ({ value: l, label: l.toUpperCase() })), onChange: setTargetLanguage }), _jsx(Button, { variant: "primary", size: "sm", onClick: runConfiguredAction, children: t(`kumiko.aiText.${dialogMode}`) })] })), dialogMode === "rewrite" && needsConfig && (_jsxs("div", { className: "mb-3 flex flex-col gap-2", children: [_jsx(ModeSwitch, { value: rewriteStyle, options: REWRITE_STYLES.map((s) => ({
|
|
124
|
+
value: s,
|
|
125
|
+
label: t(`kumiko.aiText.style.${s}`),
|
|
126
|
+
})), onChange: setRewriteStyle }), _jsx(Button, { variant: "primary", size: "sm", onClick: runConfiguredAction, children: t(`kumiko.aiText.${dialogMode}`) })] })), actionState === "loading" && (_jsx(Text, { variant: "muted", children: t("kumiko.aiText.diff.generating") })), actionState === "success" && actionResult?.type === "text" && (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { children: [_jsx(Text, { variant: "small", children: t("kumiko.aiText.diff.before") }), _jsx("p", { className: "whitespace-pre-wrap rounded-md border border-input bg-muted/40 p-2 text-sm", children: value })] }), _jsxs("div", { children: [_jsx(Text, { variant: "small", children: t("kumiko.aiText.diff.after") }), _jsx("p", { className: "whitespace-pre-wrap rounded-md border border-input p-2 text-sm", children: actionResult.text })] })] }))] })] }));
|
|
127
|
+
}
|
|
128
|
+
/** Single-line AI text field — ghost-text completion + correct/translate/
|
|
129
|
+
* rewrite toolbar. Degrades to a plain text field when `ai-text` isn't
|
|
130
|
+
* mounted server-side. */
|
|
131
|
+
export function AiTextField(props) {
|
|
132
|
+
return _jsx(AiTextCore, { ...props, multiline: false });
|
|
133
|
+
}
|
|
134
|
+
/** Multi-line variant of {@link AiTextField}. */
|
|
135
|
+
export function AiTextArea(props) {
|
|
136
|
+
return _jsx(AiTextCore, { ...props, multiline: true });
|
|
137
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type ChartWindow = {
|
|
2
|
+
readonly startMs: number;
|
|
3
|
+
readonly endMs: number;
|
|
4
|
+
};
|
|
5
|
+
export type StackedAreaRangeOption = {
|
|
6
|
+
readonly value: string;
|
|
7
|
+
/** Translated by the caller. */
|
|
8
|
+
readonly label: string;
|
|
9
|
+
/** Window length in months; omitted shows the full window. */
|
|
10
|
+
readonly months?: number;
|
|
11
|
+
};
|
|
12
|
+
export type StackedAreaRanges = {
|
|
13
|
+
readonly options: readonly StackedAreaRangeOption[];
|
|
14
|
+
readonly default: string;
|
|
15
|
+
};
|
|
16
|
+
export type StackedAreaWindowSelection = {
|
|
17
|
+
readonly kind: "range";
|
|
18
|
+
readonly value: string;
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: "brush";
|
|
21
|
+
readonly startMs: number;
|
|
22
|
+
readonly endMs: number;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "from-today";
|
|
25
|
+
};
|
|
26
|
+
export type StackedAreaInitialWindow = "default-range" | "from-today";
|
|
27
|
+
export type StackedAreaDateFormat = "day" | "month";
|
|
28
|
+
export declare function stackedAreaDateFormatFor(window: ChartWindow): StackedAreaDateFormat;
|
|
29
|
+
export declare function addUtcMonths(atMs: number, months: number): number;
|
|
30
|
+
export declare function rangeWindow(months: number, anchorMs: number, firstMs: number, lastMs: number): ChartWindow;
|
|
31
|
+
export declare function pointsWithin<T extends {
|
|
32
|
+
readonly atMs: number;
|
|
33
|
+
}>(points: readonly T[], window: ChartWindow): readonly T[];
|
|
34
|
+
export declare function initialWindowSelection(ranges: StackedAreaRanges | undefined, initialWindow?: StackedAreaInitialWindow): StackedAreaWindowSelection | undefined;
|
|
35
|
+
export declare function resolveStackedAreaWindow({ selection, ranges, brush, todayMs, bucketTimes, fullWindow, }: {
|
|
36
|
+
readonly selection: StackedAreaWindowSelection | undefined;
|
|
37
|
+
readonly ranges: StackedAreaRanges | undefined;
|
|
38
|
+
readonly brush: boolean;
|
|
39
|
+
readonly todayMs: number | undefined;
|
|
40
|
+
/** Sorted ascending. */
|
|
41
|
+
readonly bucketTimes: readonly number[];
|
|
42
|
+
readonly fullWindow: ChartWindow;
|
|
43
|
+
}): ChartWindow;
|
|
44
|
+
/** Bucket closest to a horizontal position given as a 0..1 fraction of the window. */
|
|
45
|
+
export declare function bucketIndexAtFraction(times: readonly number[], window: ChartWindow, fraction: number): number;
|
|
46
|
+
export declare function nearestIndex(times: readonly number[], atMs: number): number;
|
|
47
|
+
/** Snaps a brush drag to bucket times, keeping start < end by at least one
|
|
48
|
+
* bucket; `anchor` is the edge the user holds and therefore stays put. */
|
|
49
|
+
export declare function snapBrushWindow(times: readonly number[], startMs: number, endMs: number, anchor?: "start" | "end"): ChartWindow;
|
|
50
|
+
/** Moves a snapped window by `shiftMs`: only the start snaps, the width in
|
|
51
|
+
* bucket indices stays constant so uneven bucket gaps cannot resize it. */
|
|
52
|
+
export declare function snapMovedBrushWindow(times: readonly number[], originStartMs: number, originEndMs: number, shiftMs: number): ChartWindow;
|
|
53
|
+
/** Calendar-aligned tick times (month/day starts in `timeZone`, default UTC)
|
|
54
|
+
* strictly inside the window: the smallest
|
|
55
|
+
* step whose inner ticks fit `maxLabels - 2`, keep one label slot
|
|
56
|
+
* (`span / maxLabels`) between inner neighbours and `EDGE_GAP_SLOTS` to the
|
|
57
|
+
* start/end labels. If no step fits, the coarsest one is thinned greedily. */
|
|
58
|
+
export declare function stackedAreaAxisTicks(window: ChartWindow, maxLabels: number, format: StackedAreaDateFormat, timeZone?: string): readonly number[];
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Pure window math for StackedAreaChart: range presets anchored at today and
|
|
2
|
+
// brush selections snapped to bucket times.
|
|
3
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
4
|
+
const MONTH_FORMAT_MIN_SPAN_MONTHS = 18;
|
|
5
|
+
export function stackedAreaDateFormatFor(window) {
|
|
6
|
+
return window.endMs >= addUtcMonths(window.startMs, MONTH_FORMAT_MIN_SPAN_MONTHS)
|
|
7
|
+
? "month"
|
|
8
|
+
: "day";
|
|
9
|
+
}
|
|
10
|
+
export function addUtcMonths(atMs, months) {
|
|
11
|
+
return Temporal.Instant.fromEpochMilliseconds(atMs).toZonedDateTimeISO("UTC").add({ months })
|
|
12
|
+
.epochMilliseconds;
|
|
13
|
+
}
|
|
14
|
+
// Same window as a "1/3/5 years" switch over a plan: `months` ahead of today,
|
|
15
|
+
// pulled back when the data ends earlier, never before the first bucket.
|
|
16
|
+
// An anchor so far before the data that the window ends ahead of the first
|
|
17
|
+
// bucket falls back to the full window instead of an inverted one.
|
|
18
|
+
export function rangeWindow(months, anchorMs, firstMs, lastMs) {
|
|
19
|
+
const endMs = Math.min(lastMs, addUtcMonths(anchorMs, months));
|
|
20
|
+
if (endMs < firstMs)
|
|
21
|
+
return { startMs: firstMs, endMs: lastMs };
|
|
22
|
+
return { startMs: Math.max(firstMs, addUtcMonths(endMs, -months)), endMs };
|
|
23
|
+
}
|
|
24
|
+
export function pointsWithin(points, window) {
|
|
25
|
+
return points.filter((p) => p.atMs >= window.startMs && p.atMs <= window.endMs);
|
|
26
|
+
}
|
|
27
|
+
export function initialWindowSelection(ranges, initialWindow = "default-range") {
|
|
28
|
+
if (initialWindow === "from-today")
|
|
29
|
+
return { kind: "from-today" };
|
|
30
|
+
return ranges === undefined ? undefined : { kind: "range", value: ranges.default };
|
|
31
|
+
}
|
|
32
|
+
function clampMs(value, minMs, maxMs) {
|
|
33
|
+
return Math.max(minMs, Math.min(maxMs, value));
|
|
34
|
+
}
|
|
35
|
+
function windowFromToday(todayMs, firstMs, lastMs, fullWindow) {
|
|
36
|
+
if (todayMs === undefined || todayMs >= lastMs)
|
|
37
|
+
return fullWindow;
|
|
38
|
+
return { startMs: clampMs(todayMs, firstMs, lastMs), endMs: lastMs };
|
|
39
|
+
}
|
|
40
|
+
export function resolveStackedAreaWindow({ selection, ranges, brush, todayMs, bucketTimes, fullWindow, }) {
|
|
41
|
+
const firstMs = bucketTimes[0];
|
|
42
|
+
const lastMs = bucketTimes[bucketTimes.length - 1];
|
|
43
|
+
if (firstMs === undefined || lastMs === undefined)
|
|
44
|
+
return fullWindow;
|
|
45
|
+
if (selection?.kind === "brush") {
|
|
46
|
+
const startMs = clampMs(Math.min(selection.startMs, selection.endMs), firstMs, lastMs);
|
|
47
|
+
const endMs = clampMs(Math.max(selection.startMs, selection.endMs), firstMs, lastMs);
|
|
48
|
+
return { startMs, endMs };
|
|
49
|
+
}
|
|
50
|
+
if (selection?.kind === "range") {
|
|
51
|
+
const months = ranges?.options.find((option) => option.value === selection.value)?.months;
|
|
52
|
+
if (months === undefined)
|
|
53
|
+
return fullWindow;
|
|
54
|
+
return rangeWindow(months, todayMs ?? lastMs, firstMs, lastMs);
|
|
55
|
+
}
|
|
56
|
+
if (selection?.kind === "from-today" || (brush && ranges === undefined)) {
|
|
57
|
+
return windowFromToday(todayMs, firstMs, lastMs, fullWindow);
|
|
58
|
+
}
|
|
59
|
+
return fullWindow;
|
|
60
|
+
}
|
|
61
|
+
/** Bucket closest to a horizontal position given as a 0..1 fraction of the window. */
|
|
62
|
+
export function bucketIndexAtFraction(times, window, fraction) {
|
|
63
|
+
const clamped = Math.max(0, Math.min(1, fraction));
|
|
64
|
+
return nearestIndex(times, window.startMs + clamped * (window.endMs - window.startMs));
|
|
65
|
+
}
|
|
66
|
+
export function nearestIndex(times, atMs) {
|
|
67
|
+
let best = 0;
|
|
68
|
+
for (let i = 1; i < times.length; i++) {
|
|
69
|
+
const current = times[i];
|
|
70
|
+
const bestTime = times[best];
|
|
71
|
+
if (current === undefined || bestTime === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
if (Math.abs(current - atMs) < Math.abs(bestTime - atMs))
|
|
74
|
+
best = i;
|
|
75
|
+
}
|
|
76
|
+
return best;
|
|
77
|
+
}
|
|
78
|
+
/** Snaps a brush drag to bucket times, keeping start < end by at least one
|
|
79
|
+
* bucket; `anchor` is the edge the user holds and therefore stays put. */
|
|
80
|
+
export function snapBrushWindow(times, startMs, endMs, anchor = "start") {
|
|
81
|
+
const lastIndex = times.length - 1;
|
|
82
|
+
if (lastIndex < 1)
|
|
83
|
+
return { startMs, endMs };
|
|
84
|
+
let startIndex = nearestIndex(times, Math.min(startMs, endMs));
|
|
85
|
+
let endIndex = nearestIndex(times, Math.max(startMs, endMs));
|
|
86
|
+
if (endIndex <= startIndex) {
|
|
87
|
+
if (anchor === "start") {
|
|
88
|
+
endIndex = Math.min(lastIndex, startIndex + 1);
|
|
89
|
+
startIndex = endIndex - 1;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
startIndex = Math.max(0, endIndex - 1);
|
|
93
|
+
endIndex = startIndex + 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { startMs: times[startIndex] ?? startMs, endMs: times[endIndex] ?? endMs };
|
|
97
|
+
}
|
|
98
|
+
/** Moves a snapped window by `shiftMs`: only the start snaps, the width in
|
|
99
|
+
* bucket indices stays constant so uneven bucket gaps cannot resize it. */
|
|
100
|
+
export function snapMovedBrushWindow(times, originStartMs, originEndMs, shiftMs) {
|
|
101
|
+
const lastIndex = times.length - 1;
|
|
102
|
+
if (lastIndex < 1)
|
|
103
|
+
return { startMs: originStartMs + shiftMs, endMs: originEndMs + shiftMs };
|
|
104
|
+
const widthInBuckets = nearestIndex(times, originEndMs) - nearestIndex(times, originStartMs);
|
|
105
|
+
const startIndex = Math.min(nearestIndex(times, originStartMs + shiftMs), Math.max(0, lastIndex - widthInBuckets));
|
|
106
|
+
const endIndex = Math.min(lastIndex, startIndex + widthInBuckets);
|
|
107
|
+
return {
|
|
108
|
+
startMs: times[startIndex] ?? originStartMs + shiftMs,
|
|
109
|
+
endMs: times[endIndex] ?? originEndMs + shiftMs,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const MONTH_TICK_STEPS = [1, 2, 3, 6, 12, 24, 36, 60, 120];
|
|
113
|
+
const DAY_TICK_STEPS = [1, 2, 7, 14];
|
|
114
|
+
const DAY_MS = 86_400_000;
|
|
115
|
+
function monthTicks(window, step, timeZone) {
|
|
116
|
+
const startOfMonth = Temporal.Instant.fromEpochMilliseconds(window.startMs)
|
|
117
|
+
.toZonedDateTimeISO(timeZone)
|
|
118
|
+
.startOfDay()
|
|
119
|
+
.with({ day: 1 });
|
|
120
|
+
const ticks = [];
|
|
121
|
+
for (let cursor = startOfMonth;; cursor = cursor.add({ months: 1 })) {
|
|
122
|
+
const atMs = cursor.epochMilliseconds;
|
|
123
|
+
if (atMs >= window.endMs)
|
|
124
|
+
break;
|
|
125
|
+
if (atMs > window.startMs && (cursor.year * 12 + cursor.month - 1) % step === 0) {
|
|
126
|
+
ticks.push(atMs);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return ticks;
|
|
130
|
+
}
|
|
131
|
+
// Local day starts: DST days are not 24h, so the cursor advances by calendar
|
|
132
|
+
// day. The step phase uses the local day number, not epoch-ms modulo.
|
|
133
|
+
function dayTicks(window, step, timeZone) {
|
|
134
|
+
const ticks = [];
|
|
135
|
+
const localDayNumberOf = (day) => Math.round((day.epochMilliseconds + day.offsetNanoseconds / 1e6) / DAY_MS);
|
|
136
|
+
let cursor = Temporal.Instant.fromEpochMilliseconds(window.startMs)
|
|
137
|
+
.toZonedDateTimeISO(timeZone)
|
|
138
|
+
.startOfDay();
|
|
139
|
+
while (localDayNumberOf(cursor) % step !== 0)
|
|
140
|
+
cursor = cursor.add({ days: 1 }).startOfDay();
|
|
141
|
+
for (; cursor.epochMilliseconds < window.endMs; cursor = cursor.add({ days: step }).startOfDay()) {
|
|
142
|
+
if (cursor.epochMilliseconds > window.startMs)
|
|
143
|
+
ticks.push(cursor.epochMilliseconds);
|
|
144
|
+
}
|
|
145
|
+
return ticks;
|
|
146
|
+
}
|
|
147
|
+
function hasRoomBetweenLabels(window, ticks, slotMs) {
|
|
148
|
+
const positions = [window.startMs, ...ticks, window.endMs];
|
|
149
|
+
return positions.every((atMs, i) => i === 0 || atMs - (positions[i - 1] ?? atMs) >= slotMs);
|
|
150
|
+
}
|
|
151
|
+
function thinToSlots(window, ticks, slotMs) {
|
|
152
|
+
const kept = [];
|
|
153
|
+
let previous = window.startMs;
|
|
154
|
+
for (const atMs of ticks) {
|
|
155
|
+
if (atMs - previous >= slotMs && window.endMs - atMs >= slotMs) {
|
|
156
|
+
kept.push(atMs);
|
|
157
|
+
previous = atMs;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return kept;
|
|
161
|
+
}
|
|
162
|
+
// Start/end labels are edge-aligned (a full label width inward) while inner
|
|
163
|
+
// ticks are centred, so the edge gap needs 1.5 slots for the same clearance.
|
|
164
|
+
const EDGE_GAP_SLOTS = 1.5;
|
|
165
|
+
/** Calendar-aligned tick times (month/day starts in `timeZone`, default UTC)
|
|
166
|
+
* strictly inside the window: the smallest
|
|
167
|
+
* step whose inner ticks fit `maxLabels - 2`, keep one label slot
|
|
168
|
+
* (`span / maxLabels`) between inner neighbours and `EDGE_GAP_SLOTS` to the
|
|
169
|
+
* start/end labels. If no step fits, the coarsest one is thinned greedily. */
|
|
170
|
+
export function stackedAreaAxisTicks(window, maxLabels, format, timeZone = "UTC") {
|
|
171
|
+
const span = window.endMs - window.startMs;
|
|
172
|
+
if (span <= 0)
|
|
173
|
+
return [];
|
|
174
|
+
const slotMs = span / maxLabels;
|
|
175
|
+
const edgeGapMs = slotMs * EDGE_GAP_SLOTS;
|
|
176
|
+
const innerBudget = Math.max(0, maxLabels - 2);
|
|
177
|
+
const candidates = [
|
|
178
|
+
...(format === "day"
|
|
179
|
+
? DAY_TICK_STEPS.map((step) => () => dayTicks(window, step, timeZone))
|
|
180
|
+
: []),
|
|
181
|
+
...MONTH_TICK_STEPS.map((step) => () => monthTicks(window, step, timeZone)),
|
|
182
|
+
];
|
|
183
|
+
let ticks = [];
|
|
184
|
+
for (const candidate of candidates) {
|
|
185
|
+
ticks = candidate().filter((atMs) => atMs - window.startMs >= edgeGapMs && window.endMs - atMs >= edgeGapMs);
|
|
186
|
+
if (ticks.length <= innerBudget && hasRoomBetweenLabels(window, ticks, slotMs))
|
|
187
|
+
return ticks;
|
|
188
|
+
}
|
|
189
|
+
return thinToSlots(window, ticks, slotMs).slice(0, innerBudget);
|
|
190
|
+
}
|