@cosmicdrift/kumiko-renderer 0.374.0 → 0.378.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 +10 -7
- 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/form-derivation-patch.d.ts +8 -0
- package/dist/components/form-derivation-patch.js +24 -0
- package/dist/components/form-screen-layout.d.ts +13 -0
- package/dist/components/form-screen-layout.js +13 -0
- 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 +6 -3
- package/dist/components/render-edit.d.ts +1 -1
- package/dist/components/render-edit.js +18 -3
- package/dist/components/render-field.d.ts +1 -1
- package/dist/components/render-field.js +12 -2
- 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/file-ids-of.d.ts +1 -0
- package/dist/file-ids-of.js +7 -0
- package/dist/flow-diagram/icons.d.ts +3 -0
- package/dist/flow-diagram/icons.js +14 -0
- package/dist/flow-diagram/index.d.ts +2 -0
- package/dist/flow-diagram/index.js +1 -0
- package/dist/flow-diagram/render.d.ts +3 -0
- package/dist/flow-diagram/render.js +234 -0
- package/dist/flow-diagram/types.d.ts +18 -0
- package/dist/flow-diagram/types.js +1 -0
- 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 +21 -0
- package/dist/headless/view-model/edit.js +512 -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-defaults.js +6 -0
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -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 +45 -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 +31 -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/file-value-list.d.ts +3 -0
- package/dist/web/primitives/file-value-list.js +22 -0
- package/dist/web/primitives/index.d.ts +34 -0
- package/dist/web/primitives/index.js +2023 -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/bounded-load-cache.d.ts +1 -0
- package/dist/web/widgets/bounded-load-cache.js +25 -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 +177 -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 +28 -0
- package/dist/web/widgets/index.js +31 -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/pdf-preview.d.ts +10 -0
- package/dist/web/widgets/pdf-preview.js +140 -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 +114 -7
- package/src/changes.json +92 -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 +405 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Delegates keyboard nav + ARIA (role=tablist/tab) to Radix instead of hand-rolling it.
|
|
3
|
+
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs.js";
|
|
6
|
+
// The underline is a real border-b-2 (board) instead of the vendored ::after bar, so the
|
|
7
|
+
// vendored active/dark border resets are overridden with identical variant chains.
|
|
8
|
+
const TAB_TRIGGER_CLASS = "h-full shrink-0 flex-none whitespace-nowrap rounded-none border-0 border-b-2 border-transparent focus-visible:border-transparent bg-transparent px-0 py-0 text-sm font-medium text-foreground-secondary after:hidden hover:text-foreground data-[state=active]:border-primary data-[state=active]:focus-visible:border-primary data-[state=active]:bg-transparent data-[state=active]:font-semibold data-[state=active]:text-foreground dark:text-foreground-secondary dark:data-[state=active]:border-primary dark:data-[state=active]:bg-transparent dark:data-[state=active]:text-foreground dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-primary";
|
|
9
|
+
// A single fade-width both edges share — mask-image needs the same offset
|
|
10
|
+
// on each side or the gradient reads as lopsided.
|
|
11
|
+
const EDGE_FADE_PX = 24;
|
|
12
|
+
function edgeFadeMaskImage(start, end) {
|
|
13
|
+
if (start && end) {
|
|
14
|
+
return `linear-gradient(to right, transparent, black ${EDGE_FADE_PX}px, black calc(100% - ${EDGE_FADE_PX}px), transparent)`;
|
|
15
|
+
}
|
|
16
|
+
if (start)
|
|
17
|
+
return `linear-gradient(to right, transparent, black ${EDGE_FADE_PX}px)`;
|
|
18
|
+
if (end)
|
|
19
|
+
return `linear-gradient(to left, transparent, black ${EDGE_FADE_PX}px)`;
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
export function DefaultTabs({ items, activeId, onSelect, testId }) {
|
|
23
|
+
const scrollerRef = useRef(null);
|
|
24
|
+
// Two primitive booleans instead of one object: setState with a fresh
|
|
25
|
+
// object on every measurement would always change reference and force
|
|
26
|
+
// another render — with the unconditional-per-render effect below that
|
|
27
|
+
// would loop forever. Primitive setState lets React bail out once the
|
|
28
|
+
// value stops changing.
|
|
29
|
+
const [scrollStart, setScrollStart] = useState(false);
|
|
30
|
+
const [scrollEnd, setScrollEnd] = useState(false);
|
|
31
|
+
const updateScrollEdges = useCallback(() => {
|
|
32
|
+
const el = scrollerRef.current;
|
|
33
|
+
if (el === null) {
|
|
34
|
+
setScrollStart(false);
|
|
35
|
+
setScrollEnd(false);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const maxScrollLeft = el.scrollWidth - el.clientWidth;
|
|
39
|
+
// 1px slack absorbs sub-pixel layout rounding that would otherwise leave
|
|
40
|
+
// a fade permanently on for a strip that doesn't visually overflow.
|
|
41
|
+
setScrollStart(el.scrollLeft > 1);
|
|
42
|
+
setScrollEnd(el.scrollLeft < maxScrollLeft - 1);
|
|
43
|
+
}, []);
|
|
44
|
+
// Gated on [activeId] instead of running unconditionally: an unconditional
|
|
45
|
+
// effect also re-ran this on every render, including the render triggered
|
|
46
|
+
// by the scroll listener's own setScrollStart/End below — so scrolling the
|
|
47
|
+
// strip by hand immediately snapped it back to the active tab, making it
|
|
48
|
+
// impossible to reach any other tab by scrolling (fw#follow-up). Only a
|
|
49
|
+
// mount or an actual activeId change should force the active tab back
|
|
50
|
+
// into view.
|
|
51
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: activeId is the intended re-run trigger even though the DOM query below doesn't reference it directly; updateScrollEdges is a stable useCallback([]) ref and doesn't need to be listed.
|
|
52
|
+
useLayoutEffect(() => {
|
|
53
|
+
const el = scrollerRef.current;
|
|
54
|
+
if (el === null)
|
|
55
|
+
return;
|
|
56
|
+
// Radix marks the active trigger via aria-selected — reading it back
|
|
57
|
+
// through the DOM avoids threading a ref through the vendored
|
|
58
|
+
// TabsTrigger wrapper for every item.
|
|
59
|
+
const trigger = el.querySelector('[role="tab"][aria-selected="true"]');
|
|
60
|
+
if (trigger !== null) {
|
|
61
|
+
const triggerStart = trigger.offsetLeft;
|
|
62
|
+
const triggerEnd = triggerStart + trigger.offsetWidth;
|
|
63
|
+
// scrollIntoView would also scroll the page vertically to reveal the
|
|
64
|
+
// trigger — setting scrollLeft directly touches only this strip.
|
|
65
|
+
if (triggerStart < el.scrollLeft) {
|
|
66
|
+
el.scrollLeft = triggerStart;
|
|
67
|
+
}
|
|
68
|
+
else if (triggerEnd > el.scrollLeft + el.clientWidth) {
|
|
69
|
+
el.scrollLeft = triggerEnd - el.clientWidth;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
updateScrollEdges();
|
|
73
|
+
}, [activeId]);
|
|
74
|
+
// Edge-fade measurement stays independent of activeId: a changing item
|
|
75
|
+
// count can change scrollWidth without activeId changing (e.g. a count
|
|
76
|
+
// badge finishing a load), and this must not fight the scroll-to-active
|
|
77
|
+
// effect above by re-running on every scroll-driven render.
|
|
78
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: items.length is the intended re-run trigger even though the effect body doesn't reference it directly.
|
|
79
|
+
useLayoutEffect(() => {
|
|
80
|
+
updateScrollEdges();
|
|
81
|
+
}, [items.length, updateScrollEdges]);
|
|
82
|
+
useLayoutEffect(() => {
|
|
83
|
+
const el = scrollerRef.current;
|
|
84
|
+
if (el === null)
|
|
85
|
+
return;
|
|
86
|
+
el.addEventListener("scroll", updateScrollEdges, { passive: true });
|
|
87
|
+
if (typeof ResizeObserver === "undefined") {
|
|
88
|
+
return () => el.removeEventListener("scroll", updateScrollEdges);
|
|
89
|
+
}
|
|
90
|
+
// Observes both the scroller and the list: a growing TabsList (e.g. a
|
|
91
|
+
// count badge finishing a load) can change scrollWidth without the
|
|
92
|
+
// scroller's own box ever resizing.
|
|
93
|
+
const observer = new ResizeObserver(updateScrollEdges);
|
|
94
|
+
observer.observe(el);
|
|
95
|
+
const list = el.firstElementChild;
|
|
96
|
+
if (list !== null)
|
|
97
|
+
observer.observe(list);
|
|
98
|
+
return () => {
|
|
99
|
+
el.removeEventListener("scroll", updateScrollEdges);
|
|
100
|
+
observer.disconnect();
|
|
101
|
+
};
|
|
102
|
+
}, [updateScrollEdges]);
|
|
103
|
+
const maskImage = edgeFadeMaskImage(scrollStart, scrollEnd);
|
|
104
|
+
return (_jsx(Tabs, { value: activeId, onValueChange: onSelect, "data-testid": testId, className: "min-w-0", children: _jsx("div", { ref: scrollerRef, "data-scroll-start": scrollStart ? "" : undefined, "data-scroll-end": scrollEnd ? "" : undefined, className: "relative min-w-0 overflow-x-auto border-b border-border px-6 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", style: maskImage !== undefined ? { maskImage, WebkitMaskImage: maskImage } : undefined, children: _jsx(TabsList, { variant: "line", className: "h-11 gap-6 p-0", children: items.map((item) => (_jsxs(TabsTrigger, { value: item.id, className: TAB_TRIGGER_CLASS, "aria-controls": undefined, "data-testid": testId !== undefined ? `${testId}-${item.id}` : undefined, children: [item.label, item.count !== undefined && (_jsx("span", { className: cn("ml-1.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full px-[5px] text-xs font-medium", item.id === activeId
|
|
105
|
+
? "bg-primary/10 text-foreground"
|
|
106
|
+
: "bg-status-neutral-surface text-foreground-secondary"), "data-testid": testId !== undefined ? `${testId}-${item.id}-count` : undefined, children: item.count }))] }, item.id))) }) }) }));
|
|
107
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/** Form-State → datetime-local-Format (`yyyy-MM-ddTHH:mm`).
|
|
3
|
+
* UTC-Instants (mit `Z`/Offset) werden in lokale Wall-Clock
|
|
4
|
+
* umgerechnet; offset-lose Werte nur auf Minuten gekürzt. */
|
|
5
|
+
export declare function timestampToInputValue(value: string): string;
|
|
6
|
+
/** datetime-local-Wert → Wire-Format. wallClock=true reicht die
|
|
7
|
+
* Wall-Clock unverändert durch; sonst wird die lokale Zeit als
|
|
8
|
+
* UTC-Instant mit `Z`-Suffix emittiert. */
|
|
9
|
+
export declare function inputValueToTimestamp(raw: string, wallClock: boolean): string | undefined;
|
|
10
|
+
export type TimestampInputProps = {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly value: string;
|
|
14
|
+
readonly onChange: (v: string | undefined) => void;
|
|
15
|
+
readonly wallClock?: boolean;
|
|
16
|
+
readonly disabled?: boolean;
|
|
17
|
+
readonly required?: boolean;
|
|
18
|
+
readonly hasError?: boolean;
|
|
19
|
+
readonly ariaLabel?: string;
|
|
20
|
+
readonly locale?: string;
|
|
21
|
+
/** Untere/obere Grenze als ISO-Datetime. Begrenzt den Kalender auf
|
|
22
|
+
* Tages-Granularität; die exakte Uhrzeit-Grenze setzt die Zod-Validierung
|
|
23
|
+
* durch. */
|
|
24
|
+
readonly min?: string;
|
|
25
|
+
readonly max?: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function TimestampInput({ id, name, value, onChange, wallClock, disabled, required, hasError, ariaLabel, locale, min, max, }: TimestampInputProps): ReactNode;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// TimestampInput (kind:"timestamp") — gemeinsame Datums-Eingabe (DateField,
|
|
3
|
+
// Tippen + Jahres-Kalender) plus ein Uhrzeit-Input. Bis #369 war dies ein
|
|
4
|
+
// natives <input type="datetime-local"> — tippbar, aber mit nicht-stylebarem
|
|
5
|
+
// Browser-Picker ohne Jahres-Navigation und divergent zu DateInput.
|
|
6
|
+
//
|
|
7
|
+
// Die Wire-Konvertierung bleibt unverändert: der Server validiert timestamp
|
|
8
|
+
// als ISO-UTC mit `Z` (z.iso.datetime()) bzw. als Wall-Clock ohne Offset bei
|
|
9
|
+
// locatedTimestamps (z.iso.datetime({ local: true })). Intern rechnen wir
|
|
10
|
+
// über die datetime-local-Form `yyyy-MM-ddTHH:mm`; timestampToInputValue /
|
|
11
|
+
// inputValueToTimestamp kapseln die UTC↔Wall-Clock-Umrechnung (Bug-Bash-2,
|
|
12
|
+
// 2026-06-08) und sind separat getestet.
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
import { useTranslation } from "../../index.js";
|
|
15
|
+
import { cn } from "../lib/cn.js";
|
|
16
|
+
import { DateField } from "./date-field.js";
|
|
17
|
+
const LOCAL_MINUTES = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
18
|
+
const HAS_OFFSET = /(?:Z|[+-]\d{2}:\d{2})$/;
|
|
19
|
+
function pad(n) {
|
|
20
|
+
return String(n).padStart(2, "0");
|
|
21
|
+
}
|
|
22
|
+
/** Form-State → datetime-local-Format (`yyyy-MM-ddTHH:mm`).
|
|
23
|
+
* UTC-Instants (mit `Z`/Offset) werden in lokale Wall-Clock
|
|
24
|
+
* umgerechnet; offset-lose Werte nur auf Minuten gekürzt. */
|
|
25
|
+
export function timestampToInputValue(value) {
|
|
26
|
+
if (value === "")
|
|
27
|
+
return "";
|
|
28
|
+
if (HAS_OFFSET.test(value)) {
|
|
29
|
+
const d = new Date(value);
|
|
30
|
+
if (Number.isNaN(d.getTime()))
|
|
31
|
+
return "";
|
|
32
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
33
|
+
}
|
|
34
|
+
const m = LOCAL_MINUTES.exec(value);
|
|
35
|
+
return m !== null ? m[0] : "";
|
|
36
|
+
}
|
|
37
|
+
/** datetime-local-Wert → Wire-Format. wallClock=true reicht die
|
|
38
|
+
* Wall-Clock unverändert durch; sonst wird die lokale Zeit als
|
|
39
|
+
* UTC-Instant mit `Z`-Suffix emittiert. */
|
|
40
|
+
export function inputValueToTimestamp(raw, wallClock) {
|
|
41
|
+
if (raw === "")
|
|
42
|
+
return undefined;
|
|
43
|
+
if (wallClock)
|
|
44
|
+
return raw;
|
|
45
|
+
// Offset-loser Datetime-String wird von Date als LOKALE Zeit geparst
|
|
46
|
+
// (ES2020+) — genau die Semantik die datetime-local liefert.
|
|
47
|
+
const d = new Date(raw);
|
|
48
|
+
if (Number.isNaN(d.getTime()))
|
|
49
|
+
return undefined;
|
|
50
|
+
// Seconds are mandatory: write schemas validate offset-bearing timestamps
|
|
51
|
+
// with z.iso.datetime(), which requires `HH:mm:ssZ`.
|
|
52
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}Z`;
|
|
53
|
+
}
|
|
54
|
+
const timeInputClass = "h-9 w-[7.5rem] shrink-0 rounded-md border border-input bg-transparent px-3 py-1 text-sm " +
|
|
55
|
+
"shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 " +
|
|
56
|
+
"focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50";
|
|
57
|
+
function toDatePart(iso) {
|
|
58
|
+
if (iso === undefined || iso === "")
|
|
59
|
+
return undefined;
|
|
60
|
+
const v = timestampToInputValue(iso).slice(0, 10);
|
|
61
|
+
return v !== "" ? v : undefined;
|
|
62
|
+
}
|
|
63
|
+
export function TimestampInput({ id, name, value, onChange, wallClock, disabled, required, hasError, ariaLabel, locale, min, max, }) {
|
|
64
|
+
const t = useTranslation();
|
|
65
|
+
const local = timestampToInputValue(value);
|
|
66
|
+
const isoDate = local !== "" ? local.slice(0, 10) : "";
|
|
67
|
+
// Uhrzeit ohne gesetztes Datum würde im Wire-Wert verschwinden — lokal
|
|
68
|
+
// halten, bis ein Datum gewählt ist. Sobald `value` ein Datum trägt, ist
|
|
69
|
+
// dessen Uhrzeit maßgeblich.
|
|
70
|
+
const [timeText, setTimeText] = useState("");
|
|
71
|
+
const effectiveTime = local !== "" ? local.slice(11, 16) : timeText;
|
|
72
|
+
function emit(nextIsoDate, nextTime) {
|
|
73
|
+
if (nextIsoDate === "") {
|
|
74
|
+
onChange(undefined);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const localStr = `${nextIsoDate}T${nextTime === "" ? "00:00" : nextTime}`;
|
|
78
|
+
onChange(inputValueToTimestamp(localStr, wallClock === true));
|
|
79
|
+
}
|
|
80
|
+
const minPart = toDatePart(min);
|
|
81
|
+
const maxPart = toDatePart(max);
|
|
82
|
+
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(DateField, { id: id, name: name, value: isoDate, onChange: (d) => emit(d ?? "", effectiveTime), ...(locale !== undefined && { locale }), ...(minPart !== undefined && { min: minPart }), ...(maxPart !== undefined && { max: maxPart }), ...(disabled !== undefined && { disabled }), ...(required !== undefined && { required }), ...(hasError !== undefined && { hasError }), ...(ariaLabel !== undefined && { ariaLabel }) }), _jsx("input", { type: "time", "aria-label": t("kumiko.field.time"), disabled: disabled, "aria-invalid": hasError === true ? true : undefined, value: effectiveTime, onChange: (e) => {
|
|
83
|
+
setTimeText(e.target.value);
|
|
84
|
+
if (isoDate !== "")
|
|
85
|
+
emit(isoDate, e.target.value);
|
|
86
|
+
}, className: cn(timeInputClass, hasError === true && "border-destructive focus-visible:ring-destructive") })] }));
|
|
87
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { StatusTone } from "../widgets/status-badge.tsx";
|
|
3
|
+
export type ToastVariant = StatusTone;
|
|
4
|
+
export type ToastOptions = {
|
|
5
|
+
readonly title: string;
|
|
6
|
+
readonly description?: string;
|
|
7
|
+
readonly variant?: ToastVariant;
|
|
8
|
+
readonly docsUrl?: string;
|
|
9
|
+
readonly docsLinkLabel?: string;
|
|
10
|
+
};
|
|
11
|
+
type ToastApi = {
|
|
12
|
+
readonly toast: (opts: ToastOptions) => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function useToast(): ToastApi;
|
|
15
|
+
export type ToastProviderProps = {
|
|
16
|
+
readonly children: ReactNode;
|
|
17
|
+
};
|
|
18
|
+
export declare function ToastProvider({ children }: ToastProviderProps): ReactNode;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Toast-Primitive auf @radix-ui/react-toast. Ein globaler Provider
|
|
3
|
+
// + ein useToast()-Hook der von überall in der App aus Toasts auslösen
|
|
4
|
+
// kann. Kein Context-Boilerplate für Konsumenten — der Hook returned
|
|
5
|
+
// einen `toast()`-Trigger, mehr Public-API gibt's nicht.
|
|
6
|
+
//
|
|
7
|
+
// Variant = StatusTone (dieselbe Farb-Familie wie StatusBadge), Default
|
|
8
|
+
// "muted" (neutral). Auto-dismiss nach 5s, manuell schließbar via X-Button.
|
|
9
|
+
// Der ARIA-Live-Region-Setup kommt komplett aus Radix.
|
|
10
|
+
import * as Primitive from "@radix-ui/react-toast";
|
|
11
|
+
import { X } from "lucide-react";
|
|
12
|
+
import { createContext, useCallback, useContext, useId, useMemo, useRef, useState, } from "react";
|
|
13
|
+
import { useTranslation } from "../../index.js";
|
|
14
|
+
import { cn } from "../lib/cn.js";
|
|
15
|
+
const ToastContext = createContext(null);
|
|
16
|
+
export function useToast() {
|
|
17
|
+
const ctx = useContext(ToastContext);
|
|
18
|
+
if (ctx === null) {
|
|
19
|
+
// Nicht throwen — Tests die einen Component ohne Provider rendern
|
|
20
|
+
// sollen nicht crashen. No-op statt fehlerhaftes Mount.
|
|
21
|
+
return { toast: () => undefined };
|
|
22
|
+
}
|
|
23
|
+
return ctx;
|
|
24
|
+
}
|
|
25
|
+
export function ToastProvider({ children }) {
|
|
26
|
+
const [entries, setEntries] = useState([]);
|
|
27
|
+
// Monoton wachsender Counter via useRef — useState hätte einen
|
|
28
|
+
// closure-Bug: zwei toast()-Calls im selben Tick lesen denselben
|
|
29
|
+
// alten Wert aus dem Closure und kollidieren bei der ID. Ref ist
|
|
30
|
+
// synchron-mutabel, jeder Aufruf bekommt eine garantiert neue ID.
|
|
31
|
+
const idPrefix = useId();
|
|
32
|
+
const counterRef = useRef(0);
|
|
33
|
+
const toast = useCallback((opts) => {
|
|
34
|
+
counterRef.current += 1;
|
|
35
|
+
const id = `${idPrefix}-${counterRef.current}`;
|
|
36
|
+
setEntries((current) => [...current, { ...opts, id }]);
|
|
37
|
+
}, [idPrefix]);
|
|
38
|
+
const api = useMemo(() => ({ toast }), [toast]);
|
|
39
|
+
return (_jsx(ToastContext.Provider, { value: api, children: _jsxs(Primitive.Provider, { swipeDirection: "right", duration: 5000, children: [children, entries.map((entry) => (_jsx(ToastItem, { entry: entry, onClose: () => setEntries((current) => current.filter((e) => e.id !== entry.id)) }, entry.id))), _jsx(Primitive.Viewport, { className: cn("fixed top-0 right-0 z-[100] flex max-h-screen w-full flex-col-reverse gap-2 p-4", "sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]") })] }) }));
|
|
40
|
+
}
|
|
41
|
+
const rootClass = "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 " +
|
|
42
|
+
"overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all " +
|
|
43
|
+
"data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] " +
|
|
44
|
+
"data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none " +
|
|
45
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out " +
|
|
46
|
+
"data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full " +
|
|
47
|
+
"data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full";
|
|
48
|
+
const TONE_CLASS = {
|
|
49
|
+
ok: "border-status-ok/30 bg-status-ok/10 text-status-ok",
|
|
50
|
+
warn: "border-status-warn/30 bg-status-warn/10 text-status-warn",
|
|
51
|
+
bad: "border-status-bad/30 bg-status-bad/10 text-status-bad",
|
|
52
|
+
critical: "border-status-critical/40 bg-status-critical/15 text-status-critical",
|
|
53
|
+
muted: "border bg-background text-foreground",
|
|
54
|
+
};
|
|
55
|
+
function ToastItem({ entry, onClose, }) {
|
|
56
|
+
const t = useTranslation();
|
|
57
|
+
const learnMore = entry.docsLinkLabel ?? t("kumiko.toast.learn-more");
|
|
58
|
+
const variantClass = TONE_CLASS[entry.variant ?? "muted"];
|
|
59
|
+
return (_jsxs(Primitive.Root, { className: cn(rootClass, variantClass), onOpenChange: (open) => {
|
|
60
|
+
if (!open)
|
|
61
|
+
onClose();
|
|
62
|
+
}, children: [_jsxs("div", { className: "grid gap-1", children: [_jsx(Primitive.Title, { className: "text-sm font-semibold", children: entry.title }), entry.description !== undefined && (_jsx(Primitive.Description, { className: "text-sm opacity-90", children: entry.description })), entry.docsUrl !== undefined && (_jsx(Primitive.Action, { altText: learnMore, asChild: true, children: _jsxs("a", { href: entry.docsUrl, target: "_blank", rel: "noopener noreferrer", className: cn("text-xs underline opacity-90 hover:opacity-100", "focus:outline-none focus:ring-1 focus:ring-current rounded"), children: [learnMore, " \u2192"] }) }))] }), _jsx(Primitive.Close, { className: cn("absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity", "hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1", "group-hover:opacity-100"), "aria-label": t("kumiko.dialog.close"), children: _jsx(X, { className: "h-4 w-4" }) })] }));
|
|
63
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type TzInputProps = {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly value: string;
|
|
6
|
+
readonly onChange: (v: string | undefined) => void;
|
|
7
|
+
readonly disabled?: boolean;
|
|
8
|
+
readonly required?: boolean;
|
|
9
|
+
readonly hasError?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function TzInput({ id, name, value, onChange, disabled, required, hasError, }: TzInputProps): ReactNode;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from "../../index.js";
|
|
3
|
+
import { ComboboxInput } from "./combobox.js";
|
|
4
|
+
import { TZ_OPTIONS } from "./tz-options.js";
|
|
5
|
+
export function TzInput({ id, name, value, onChange, disabled, required, hasError, }) {
|
|
6
|
+
const t = useTranslation();
|
|
7
|
+
return (_jsx(ComboboxInput, { id: id, name: name, options: TZ_OPTIONS, value: value, onChange: (v) => onChange(v === "" ? undefined : v), placeholder: t("kumiko.field.timezone"), ...(disabled !== undefined && { disabled }), ...(required !== undefined && { required }), ...(hasError !== undefined && { hasError }) }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Curated fallback list for runtimes without Intl.supportedValuesOf (pre
|
|
2
|
+
// ES2022). Covers the most common zones; modern browsers + Bun return the
|
|
3
|
+
// full IANA list.
|
|
4
|
+
const FALLBACK_ZONES = [
|
|
5
|
+
"UTC",
|
|
6
|
+
"Europe/Berlin",
|
|
7
|
+
"Europe/London",
|
|
8
|
+
"Europe/Paris",
|
|
9
|
+
"Europe/Madrid",
|
|
10
|
+
"America/New_York",
|
|
11
|
+
"America/Los_Angeles",
|
|
12
|
+
"America/Sao_Paulo",
|
|
13
|
+
"Asia/Tokyo",
|
|
14
|
+
"Asia/Singapore",
|
|
15
|
+
"Australia/Sydney",
|
|
16
|
+
];
|
|
17
|
+
export const TZ_OPTIONS = (typeof Intl.supportedValuesOf === "function" ? Intl.supportedValuesOf("timeZone") : FALLBACK_ZONES).map((zone) => ({ value: zone, label: zone }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useKeyboardInset(): number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
function readKeyboardInset() {
|
|
3
|
+
const viewport = window.visualViewport;
|
|
4
|
+
if (viewport === null || viewport === undefined)
|
|
5
|
+
return 0;
|
|
6
|
+
return Math.max(0, Math.round(window.innerHeight - viewport.height - viewport.offsetTop));
|
|
7
|
+
}
|
|
8
|
+
// iOS Safari ignores `interactive-widget=resizes-content`: the layout viewport keeps its full
|
|
9
|
+
// height under the keyboard, so `fixed bottom-0` bars sit behind it. Where the key is honoured
|
|
10
|
+
// (Chromium/Android) the layout viewport shrinks too, the difference is 0 and nothing shifts.
|
|
11
|
+
export function useKeyboardInset() {
|
|
12
|
+
const [inset, setInset] = useState(0);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const viewport = window.visualViewport;
|
|
15
|
+
// skip: without visualViewport no keyboard inset is measurable
|
|
16
|
+
if (viewport === null || viewport === undefined)
|
|
17
|
+
return;
|
|
18
|
+
const update = () => setInset(readKeyboardInset());
|
|
19
|
+
update();
|
|
20
|
+
viewport.addEventListener("resize", update);
|
|
21
|
+
viewport.addEventListener("scroll", update);
|
|
22
|
+
return () => {
|
|
23
|
+
viewport.removeEventListener("resize", update);
|
|
24
|
+
viewport.removeEventListener("scroll", update);
|
|
25
|
+
};
|
|
26
|
+
}, []);
|
|
27
|
+
return inset;
|
|
28
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
// Matches ui/use-mobile.ts's MOBILE_BREAKPOINT. Kept as a separate constant
|
|
3
|
+
// because that file is vendored shadcn (regenerated via scripts/sync-shadcn.ts)
|
|
4
|
+
// and cannot be imported from without risking a future overwrite.
|
|
5
|
+
export const NARROW_VIEWPORT_BREAKPOINT_PX = 768;
|
|
6
|
+
const SM_BREAKPOINT = 640;
|
|
7
|
+
// Module-level stores keep subscribe stable, so useSyncExternalStore does not
|
|
8
|
+
// resubscribe on every render (FormFooter re-renders on each keystroke).
|
|
9
|
+
function createBelowBreakpointStore(breakpoint) {
|
|
10
|
+
const query = `(max-width: ${breakpoint - 1}px)`;
|
|
11
|
+
return {
|
|
12
|
+
subscribe: (callback) => {
|
|
13
|
+
const mql = window.matchMedia(query);
|
|
14
|
+
mql.addEventListener("change", callback);
|
|
15
|
+
return () => mql.removeEventListener("change", callback);
|
|
16
|
+
},
|
|
17
|
+
getSnapshot: () => window.matchMedia(query).matches,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const belowBreakpointStores = new Map();
|
|
21
|
+
function belowBreakpointStore(breakpoint) {
|
|
22
|
+
const cached = belowBreakpointStores.get(breakpoint);
|
|
23
|
+
if (cached !== undefined)
|
|
24
|
+
return cached;
|
|
25
|
+
const created = createBelowBreakpointStore(breakpoint);
|
|
26
|
+
belowBreakpointStores.set(breakpoint, created);
|
|
27
|
+
return created;
|
|
28
|
+
}
|
|
29
|
+
function getServerSnapshot() {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// Unlike the vendored `useIsMobile` (ui/use-mobile.ts), which only sets its
|
|
33
|
+
// result in a `useEffect` and therefore always reports `false` on the first
|
|
34
|
+
// render regardless of actual viewport, this reads the real value up front
|
|
35
|
+
// via `useSyncExternalStore` — no wrong-then-corrected first render.
|
|
36
|
+
export function useIsViewportBelow(breakpointPx) {
|
|
37
|
+
const store = belowBreakpointStore(breakpointPx);
|
|
38
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, getServerSnapshot);
|
|
39
|
+
}
|
|
40
|
+
export function useIsNarrowViewport() {
|
|
41
|
+
return useIsViewportBelow(NARROW_VIEWPORT_BREAKPOINT_PX);
|
|
42
|
+
}
|
|
43
|
+
// Tailwind's `sm` boundary: true below 640px.
|
|
44
|
+
export function useIsBelowSmViewport() {
|
|
45
|
+
return useIsViewportBelow(SM_BREAKPOINT);
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type LiveEventSubscriber } from "../../index.ts";
|
|
2
|
+
export type CreateEventSourceLiveEventsOptions = {
|
|
3
|
+
/** URL des SSE-Endpoints. Default: /api/sse (das ist wo
|
|
4
|
+
* createSseRoute im Kumiko-Server mountet). Override wenn der
|
|
5
|
+
* Mountpath divergiert. */
|
|
6
|
+
readonly url?: string;
|
|
7
|
+
/** Fires once per connection when the session ended: the server refused the SSE
|
|
8
|
+
* handshake, or opened the stream with a `session-ended` frame (stale auth
|
|
9
|
+
* cookie downgraded to anonymous). Same codes as the dispatcher path. */
|
|
10
|
+
readonly onSessionEnded?: () => void;
|
|
11
|
+
};
|
|
12
|
+
/** Liefert einen `LiveEventSubscriber` der EventSource-backed ist.
|
|
13
|
+
* Normalerweise einmal im App-Bootstrap gerufen und als value an
|
|
14
|
+
* `<LiveEventsProvider>` durchgereicht — createKumikoApp tut das. */
|
|
15
|
+
export declare function createEventSourceLiveEvents(options?: CreateEventSourceLiveEventsOptions): LiveEventSubscriber;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
|
+
import { isSessionEndedError } from "../../index.js";
|
|
3
|
+
// Wire name of the first frame on a stream downgraded from a stale auth cookie (sse-route.ts).
|
|
4
|
+
const SESSION_ENDED_EVENT = "session-ended";
|
|
5
|
+
function sessionEndedFrameCode(event) {
|
|
6
|
+
const data = propertyOf(event, "data");
|
|
7
|
+
if (typeof data !== "string")
|
|
8
|
+
return undefined;
|
|
9
|
+
try {
|
|
10
|
+
const code = propertyOf(JSON.parse(data), "code");
|
|
11
|
+
return typeof code === "string" ? code : undefined;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// skip: malformed frame, not evidence of a session end
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function hasErrorCode(body) {
|
|
19
|
+
if (typeof body !== "object" || body === null || !("error" in body))
|
|
20
|
+
return false;
|
|
21
|
+
const { error } = body;
|
|
22
|
+
return (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string");
|
|
23
|
+
}
|
|
24
|
+
// EventSource never exposes the HTTP status, so one plain fetch of the same
|
|
25
|
+
// URL tells a session end apart from an outage. Aborted right after the
|
|
26
|
+
// headers/body so a 200 doesn't hold a second stream open.
|
|
27
|
+
async function isSessionEndedRejection(url) {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
try {
|
|
30
|
+
// guard-allow: same-origin fetch — url is the app's own SSE endpoint, probed with same-origin credentials
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
credentials: "same-origin",
|
|
33
|
+
headers: { Accept: "text/event-stream" },
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
});
|
|
36
|
+
if (res.status !== 401)
|
|
37
|
+
return false;
|
|
38
|
+
const body = await res.json();
|
|
39
|
+
return hasErrorCode(body) && isSessionEndedError({ code: body.error.code, httpStatus: 401 });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// skip: probe failed (offline, unparseable body) — not evidence of a session end
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
controller.abort();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Liefert einen `LiveEventSubscriber` der EventSource-backed ist.
|
|
50
|
+
* Normalerweise einmal im App-Bootstrap gerufen und als value an
|
|
51
|
+
* `<LiveEventsProvider>` durchgereicht — createKumikoApp tut das. */
|
|
52
|
+
export function createEventSourceLiveEvents(options = {}) {
|
|
53
|
+
const url = options.url ?? "/api/sse";
|
|
54
|
+
const subscribers = new Set();
|
|
55
|
+
let source;
|
|
56
|
+
const wiredEntities = new Set();
|
|
57
|
+
// `subscribers` is a flat Set across all entities — the browser-side
|
|
58
|
+
// addEventListener(entityName, ...) gate below only filters which frames
|
|
59
|
+
// arrive at all, not which subscriber a given frame is for. Without this
|
|
60
|
+
// filter, an `invoice` subscriber would also fire on every `user` frame.
|
|
61
|
+
const handleEvent = (raw) => {
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(raw);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// skip: malformed SSE payload, drop it rather than crash all subscribers
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const event = { type: parsed.aggregateType, data: parsed };
|
|
71
|
+
for (const sub of subscribers) {
|
|
72
|
+
if (sub.entityName === parsed.aggregateType)
|
|
73
|
+
sub.listener(event);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const ensureConnected = () => {
|
|
77
|
+
// skip: EventSource already connected
|
|
78
|
+
if (source !== undefined)
|
|
79
|
+
return;
|
|
80
|
+
// skip: no window/EventSource available (SSR/non-browser), nothing to connect
|
|
81
|
+
if (typeof window === "undefined" || typeof EventSource === "undefined")
|
|
82
|
+
return;
|
|
83
|
+
const connection = new EventSource(url);
|
|
84
|
+
source = connection;
|
|
85
|
+
const { onSessionEnded } = options;
|
|
86
|
+
// skip: no session-end consumer, a refused handshake needs no probe
|
|
87
|
+
if (onSessionEnded === undefined)
|
|
88
|
+
return;
|
|
89
|
+
let sessionEndedSignalled = false;
|
|
90
|
+
const signalSessionEnded = () => {
|
|
91
|
+
// skip: the frame and the error probe can both report the same session end
|
|
92
|
+
if (sessionEndedSignalled)
|
|
93
|
+
return;
|
|
94
|
+
sessionEndedSignalled = true;
|
|
95
|
+
onSessionEnded();
|
|
96
|
+
};
|
|
97
|
+
connection.addEventListener(SESSION_ENDED_EVENT, (e) => {
|
|
98
|
+
const code = sessionEndedFrameCode(e);
|
|
99
|
+
if (code !== undefined && isSessionEndedError({ code, httpStatus: 401 }))
|
|
100
|
+
signalSessionEnded();
|
|
101
|
+
});
|
|
102
|
+
connection.addEventListener("error", () => {
|
|
103
|
+
// skip: CONNECTING means a dropped stream the browser retries itself;
|
|
104
|
+
// only a refused handshake (non-200) closes the EventSource for good.
|
|
105
|
+
if (connection.readyState !== EventSource.CLOSED)
|
|
106
|
+
return;
|
|
107
|
+
void isSessionEndedRejection(url).then((sessionEnded) => {
|
|
108
|
+
if (sessionEnded)
|
|
109
|
+
signalSessionEnded();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
const ensureListenersForEntity = (entityName) => {
|
|
114
|
+
// skip: not connected yet, listeners get wired once ensureConnected runs
|
|
115
|
+
if (source === undefined)
|
|
116
|
+
return;
|
|
117
|
+
// skip: already wired for this entity
|
|
118
|
+
if (wiredEntities.has(entityName))
|
|
119
|
+
return;
|
|
120
|
+
source.addEventListener(entityName, (e) => {
|
|
121
|
+
const data = propertyOf(e, "data");
|
|
122
|
+
if (typeof data === "string")
|
|
123
|
+
handleEvent(data);
|
|
124
|
+
});
|
|
125
|
+
wiredEntities.add(entityName);
|
|
126
|
+
};
|
|
127
|
+
const closeIfEmpty = () => {
|
|
128
|
+
// skip: subscribers remain, connection still needed
|
|
129
|
+
if (subscribers.size > 0)
|
|
130
|
+
return;
|
|
131
|
+
// skip: already closed, nothing to close
|
|
132
|
+
if (source === undefined)
|
|
133
|
+
return;
|
|
134
|
+
source.close();
|
|
135
|
+
source = undefined;
|
|
136
|
+
wiredEntities.clear();
|
|
137
|
+
};
|
|
138
|
+
return (entityName, listener) => {
|
|
139
|
+
ensureConnected();
|
|
140
|
+
ensureListenersForEntity(entityName);
|
|
141
|
+
const sub = { entityName, listener };
|
|
142
|
+
subscribers.add(sub);
|
|
143
|
+
return () => {
|
|
144
|
+
subscribers.delete(sub);
|
|
145
|
+
closeIfEmpty();
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
}
|