@cosmicdrift/kumiko-renderer 0.373.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 +58 -0
- package/dist/app/action-form-support.js +96 -0
- package/dist/app/client-actions.d.ts +46 -0
- package/dist/app/client-actions.js +60 -0
- 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/entity-list-expanded-row.js +4 -0
- package/dist/app/form-schema.d.ts +1 -1
- package/dist/app/form-schema.js +2 -2
- package/dist/app/header-actions-bar.d.ts +19 -0
- package/dist/app/header-actions-bar.js +30 -0
- package/dist/app/kumiko-screen.d.ts +1 -1
- package/dist/app/kumiko-screen.js +134 -66
- package/dist/app/list-empty-content.d.ts +2 -2
- package/dist/app/list-empty-content.js +3 -2
- 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 +10 -2
- package/dist/app/row-actions.js +85 -6
- 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 +12 -0
- package/dist/app/use-record-actions.js +30 -0
- 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 +4 -2
- package/dist/components/grid-cell-for-field.js +2 -2
- 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/related-list-section.js +16 -3
- 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 +5 -3
- package/dist/components/render-field.d.ts +5 -2
- package/dist/components/render-field.js +103 -9
- 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-defaults.js +7 -0
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -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 +53 -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 +77 -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,409 @@
|
|
|
1
|
+
import { createStore } from "../store/index.js";
|
|
2
|
+
import { groupIssuesByPath, relevantFieldIssues, zodErrorToFieldIssues } from "./zod-bridge.js";
|
|
3
|
+
// Paths without the prefix (hook-level errors, "(root)") pass through unchanged.
|
|
4
|
+
function stripServerFieldPathPrefix(issues, prefix) {
|
|
5
|
+
return issues.map((issue) => issue.path.startsWith(prefix) ? { ...issue, path: issue.path.slice(prefix.length) } : issue);
|
|
6
|
+
}
|
|
7
|
+
// Resolve one condition-value (boolean | predicate) against current values + ctx.
|
|
8
|
+
// `undefined` means "condition not declared"; the caller substitutes the
|
|
9
|
+
// field-default (visible:true, readonly:false, required:false).
|
|
10
|
+
function evalCondition(condition, fallback, values, ctx) {
|
|
11
|
+
if (condition === undefined)
|
|
12
|
+
return fallback;
|
|
13
|
+
if (typeof condition === "boolean")
|
|
14
|
+
return condition;
|
|
15
|
+
return condition(values, ctx);
|
|
16
|
+
}
|
|
17
|
+
function computeFieldStates(rules, values, ctx) {
|
|
18
|
+
if (!rules)
|
|
19
|
+
return {};
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [fieldKey, rule] of Object.entries(rules)) {
|
|
22
|
+
out[fieldKey] = {
|
|
23
|
+
visible: evalCondition(rule.visible, true, values, ctx),
|
|
24
|
+
readonly: evalCondition(rule.readonly, false, values, ctx),
|
|
25
|
+
required: evalCondition(rule.required, false, values, ctx),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
// Reference equality only. For primitive-valued fields this is exact; for
|
|
31
|
+
// object/array fields it treats "same reference" as "unchanged" — callers
|
|
32
|
+
// who mutate in place instead of swapping references (which is bad form
|
|
33
|
+
// anyway, but common in ad-hoc tests) would miss a change. Kumiko's
|
|
34
|
+
// form-controller contract is that mutators build new references.
|
|
35
|
+
// Object.is is chosen over === so NaN !== NaN doesn't mark a field dirty
|
|
36
|
+
// after you re-type the same number back in.
|
|
37
|
+
function valuesDiff(current, initial) {
|
|
38
|
+
const out = {};
|
|
39
|
+
// Iterate over BOTH sides — a field that existed on initial but was
|
|
40
|
+
// deleted from current (via `setValues({ foo: undefined })`) still
|
|
41
|
+
// counts as a change.
|
|
42
|
+
// FormValues<T> does not allow keyof-T iteration (T is generic). Cast
|
|
43
|
+
// to Record<string, unknown> for dynamic-key inspection.
|
|
44
|
+
const cur = current; // @cast-boundary form-values
|
|
45
|
+
const ini = initial; // @cast-boundary form-values
|
|
46
|
+
const o = out; // @cast-boundary form-values
|
|
47
|
+
const keys = new Set([...Object.keys(cur), ...Object.keys(ini)]);
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
const a = cur[key];
|
|
50
|
+
const b = ini[key];
|
|
51
|
+
if (!Object.is(a, b)) {
|
|
52
|
+
o[key] = a;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
// buildInitialValues seeds untouched optional fields with "" for controlled
|
|
58
|
+
// inputs; a `.optional()` server schema accepts undefined but not "".
|
|
59
|
+
// String-only: number/money seed to 0 and boolean seeds to false, and an
|
|
60
|
+
// untouched optional field of those types still submits that seed instead
|
|
61
|
+
// of being omitted — no validation error surfaces it, unlike the "" case.
|
|
62
|
+
// Fixing that needs real per-field seed-tracking (which value was actually
|
|
63
|
+
// user-set vs. defaulted), not just a wider strip predicate here.
|
|
64
|
+
function stripUntouchedEmptyStrings(values, initial, forceIncludeEmpty) {
|
|
65
|
+
const v = values; // @cast-boundary form-values
|
|
66
|
+
const ini = initial; // @cast-boundary form-values
|
|
67
|
+
let out;
|
|
68
|
+
for (const key of Object.keys(v)) {
|
|
69
|
+
// Value-based seed strip, except keys previously submitted as "" after a
|
|
70
|
+
// real clear — those must survive post-rebase submits (#1858).
|
|
71
|
+
if (!forceIncludeEmpty.has(key) && v[key] === "" && ini[key] === "") {
|
|
72
|
+
out ??= { ...v };
|
|
73
|
+
delete out[key];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return (out ?? v);
|
|
77
|
+
}
|
|
78
|
+
// Shallow-freeze so accidental mutations on the snapshot (e.g. a test
|
|
79
|
+
// writing `snapshot.values.title = "x"`) throw in strict mode instead of
|
|
80
|
+
// silently diverging from the controller's internal state. Deep-freeze
|
|
81
|
+
// would be safer but expensive — keeping it shallow matches React's own
|
|
82
|
+
// immutability expectations and leaves room for sub-controllers to manage
|
|
83
|
+
// their own sub-trees without double-frozen parents blocking them.
|
|
84
|
+
function freezeSnapshot(snapshot) {
|
|
85
|
+
return Object.freeze(snapshot);
|
|
86
|
+
}
|
|
87
|
+
function omitPayloadFields(payload, omitFields) {
|
|
88
|
+
if (!omitFields || omitFields.length === 0)
|
|
89
|
+
return payload;
|
|
90
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload))
|
|
91
|
+
return payload;
|
|
92
|
+
return Object.fromEntries(Object.entries(payload).filter(([key]) => !omitFields.includes(key)));
|
|
93
|
+
}
|
|
94
|
+
export function createFormController(options) {
|
|
95
|
+
// Shallow copy so mutating the input after creation doesn't bleed into
|
|
96
|
+
// the controller's internal state. `initial` stays fixed across the
|
|
97
|
+
// controller's lifetime unless rebase() replaces it.
|
|
98
|
+
let values = { ...options.initial };
|
|
99
|
+
let initial = { ...options.initial };
|
|
100
|
+
let errors = Object.freeze({});
|
|
101
|
+
// Keys last submitted as "" (intentional clear). Survives rebase so a later
|
|
102
|
+
// submit of other fields does not drop them (#1858).
|
|
103
|
+
const forceIncludeEmpty = new Set();
|
|
104
|
+
// ctx is cast-held as TCtx; `undefined` is valid when callers don't
|
|
105
|
+
// declare conditional predicates that depend on it.
|
|
106
|
+
let ctx = options.ctx ?? undefined;
|
|
107
|
+
// Snapshot lives in a Store — same reference across getSnapshot() calls
|
|
108
|
+
// until a mutator invalidates it via setState. React's useSyncExternalStore
|
|
109
|
+
// compares snapshot identity to decide on a re-render; the Store holds the
|
|
110
|
+
// identity stable until invalidate() swaps it for a fresh build.
|
|
111
|
+
const snapshotStore = createStore(buildSnapshot());
|
|
112
|
+
// In-flight submit tracker. When a submit() is pending, a second call
|
|
113
|
+
// awaits the same promise instead of firing a parallel write — avoids
|
|
114
|
+
// double-submission on double-click and keeps the rebase semantics
|
|
115
|
+
// coherent (rebasing twice in quick succession would mis-align the
|
|
116
|
+
// baseline with what the server actually saw).
|
|
117
|
+
let submitInFlight = null;
|
|
118
|
+
function buildSnapshot() {
|
|
119
|
+
const changes = valuesDiff(values, initial);
|
|
120
|
+
const isDirty = Object.keys(changes).length > 0;
|
|
121
|
+
const fields = Object.freeze(computeFieldStates(options.fields, values, ctx));
|
|
122
|
+
return freezeSnapshot({
|
|
123
|
+
values,
|
|
124
|
+
initial,
|
|
125
|
+
changes,
|
|
126
|
+
isDirty,
|
|
127
|
+
isUnchanged: !isDirty,
|
|
128
|
+
errors,
|
|
129
|
+
fields,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function invalidate() {
|
|
133
|
+
snapshotStore.setState(buildSnapshot());
|
|
134
|
+
}
|
|
135
|
+
// Local shared implementations so submit() can call validate/rebase
|
|
136
|
+
// without the this-in-object-literal dance. Both also expose themselves
|
|
137
|
+
// as methods on the returned controller.
|
|
138
|
+
// Clears/replaces errors for a scoped run without touching fields outside
|
|
139
|
+
// the scope — a step-2 validate() that comes back clean must not wipe
|
|
140
|
+
// step-1's still-unresolved errors from an earlier submit() (or vice
|
|
141
|
+
// versa when step-2 newly has issues).
|
|
142
|
+
function replaceScopedErrors(isInScope, grouped) {
|
|
143
|
+
if (isInScope === undefined) {
|
|
144
|
+
errors = Object.freeze(grouped);
|
|
145
|
+
// skip: unscoped replace — wholesale overwrite, nothing left to merge
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const merged = {};
|
|
149
|
+
for (const [path, issues] of Object.entries(errors)) {
|
|
150
|
+
if (!isInScope(path))
|
|
151
|
+
merged[path] = issues;
|
|
152
|
+
}
|
|
153
|
+
Object.assign(merged, grouped);
|
|
154
|
+
errors = Object.freeze(merged);
|
|
155
|
+
}
|
|
156
|
+
function runValidate(scope, opts) {
|
|
157
|
+
// See the validate() doc comment in types.ts for the scope contract.
|
|
158
|
+
// A scope entry matches its exact path and everything below it, but not
|
|
159
|
+
// siblings: `address.city` must not pull in `address.zip` (#1898).
|
|
160
|
+
const isInScope = scope === undefined
|
|
161
|
+
? undefined
|
|
162
|
+
: (path) => scope.some((entry) => path === entry || path.startsWith(`${entry}.`));
|
|
163
|
+
const includeRoot = opts?.includeRoot === true;
|
|
164
|
+
if (!options.schema) {
|
|
165
|
+
if (Object.keys(errors).length > 0) {
|
|
166
|
+
replaceScopedErrors(isInScope, {});
|
|
167
|
+
invalidate();
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
const fieldStates = computeFieldStates(options.fields, values, ctx);
|
|
172
|
+
const parsed = options.schema.safeParse(values);
|
|
173
|
+
if (parsed.success) {
|
|
174
|
+
if (Object.keys(errors).length > 0) {
|
|
175
|
+
replaceScopedErrors(isInScope, {});
|
|
176
|
+
invalidate();
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
const hiddenFields = new Set();
|
|
181
|
+
for (const [fieldKey, state] of Object.entries(fieldStates)) {
|
|
182
|
+
if (!state.visible)
|
|
183
|
+
hiddenFields.add(fieldKey);
|
|
184
|
+
}
|
|
185
|
+
// Root-level .refine() issues use path "(root)". Wizard step validate()
|
|
186
|
+
// still drops them (#1885); submit() passes includeRoot so RenderEdit
|
|
187
|
+
// fields={…} cannot silently skip cross-field rules (#1907).
|
|
188
|
+
const relevantIssues = relevantFieldIssues(zodErrorToFieldIssues(parsed.error), {
|
|
189
|
+
hiddenFields,
|
|
190
|
+
...(scope !== undefined && { scope }),
|
|
191
|
+
includeRoot,
|
|
192
|
+
});
|
|
193
|
+
if (relevantIssues.length === 0) {
|
|
194
|
+
if (Object.keys(errors).length > 0) {
|
|
195
|
+
replaceScopedErrors(isInScope, {});
|
|
196
|
+
invalidate();
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
replaceScopedErrors(isInScope, groupIssuesByPath(relevantIssues));
|
|
201
|
+
invalidate();
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
function runRebase() {
|
|
205
|
+
initial = { ...values };
|
|
206
|
+
if (Object.keys(errors).length > 0)
|
|
207
|
+
errors = Object.freeze({});
|
|
208
|
+
invalidate();
|
|
209
|
+
}
|
|
210
|
+
// Stale-submit-safe rebase. Difference to runRebase: the baseline is
|
|
211
|
+
// taken from the values that were actually SENT to the server, not
|
|
212
|
+
// from `values` at the moment the server replied. If the user typed
|
|
213
|
+
// into a field while the submit was in flight, those edits stay as
|
|
214
|
+
// dirty changes after the call — they never made it to the server, so
|
|
215
|
+
// pretending they did (via `initial = { ...values }`) would mask unsaved
|
|
216
|
+
// input. The race shows up as "user edits a field during a 2s save, sees
|
|
217
|
+
// it as saved, closes the tab — the edit is lost". This path keeps
|
|
218
|
+
// `values` untouched; only `initial` shifts to the submitted snapshot.
|
|
219
|
+
function runRebaseToSnapshot(snapped) {
|
|
220
|
+
initial = { ...snapped };
|
|
221
|
+
if (Object.keys(errors).length > 0)
|
|
222
|
+
errors = Object.freeze({});
|
|
223
|
+
invalidate();
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
getSnapshot: snapshotStore.getSnapshot,
|
|
227
|
+
subscribe: snapshotStore.subscribe,
|
|
228
|
+
setField(key, value) {
|
|
229
|
+
// skip: value unchanged, avoid notify/re-render on identical set
|
|
230
|
+
// avoids a notify + re-render for "setField with same value" which
|
|
231
|
+
// happens a lot in controlled inputs on every keystroke of an
|
|
232
|
+
// untouched field.
|
|
233
|
+
if (Object.is(values[key], value))
|
|
234
|
+
return;
|
|
235
|
+
values = { ...values, [key]: value };
|
|
236
|
+
invalidate();
|
|
237
|
+
},
|
|
238
|
+
setValues(partial) {
|
|
239
|
+
// skip: partial matches current values, avoid no-op notify
|
|
240
|
+
// partial that matches current values shouldn't fire listeners.
|
|
241
|
+
let changed = false;
|
|
242
|
+
const v = values; // @cast-boundary form-values
|
|
243
|
+
const p = partial; // @cast-boundary form-values
|
|
244
|
+
for (const k of Object.keys(p)) {
|
|
245
|
+
if (!Object.is(v[k], p[k])) {
|
|
246
|
+
changed = true;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// skip: no key in partial actually changed, avoid no-op notify
|
|
251
|
+
if (!changed)
|
|
252
|
+
return;
|
|
253
|
+
values = { ...values, ...partial };
|
|
254
|
+
invalidate();
|
|
255
|
+
},
|
|
256
|
+
clearErrors(path) {
|
|
257
|
+
if (path === undefined) {
|
|
258
|
+
// skip: no errors present, avoid no-op notify
|
|
259
|
+
if (Object.keys(errors).length === 0)
|
|
260
|
+
return;
|
|
261
|
+
errors = Object.freeze({});
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// skip: path has no error entry, avoid no-op notify
|
|
265
|
+
if (!(path in errors))
|
|
266
|
+
return;
|
|
267
|
+
const next = { ...errors };
|
|
268
|
+
delete next[path];
|
|
269
|
+
errors = Object.freeze(next);
|
|
270
|
+
}
|
|
271
|
+
invalidate();
|
|
272
|
+
},
|
|
273
|
+
setErrors(nextErrors) {
|
|
274
|
+
errors = Object.freeze({ ...nextErrors });
|
|
275
|
+
invalidate();
|
|
276
|
+
},
|
|
277
|
+
validate: runValidate,
|
|
278
|
+
reset() {
|
|
279
|
+
const alreadyClean = !snapshotStore.getSnapshot().isDirty && Object.keys(errors).length === 0;
|
|
280
|
+
// skip: already at baseline with no errors, no-op reset
|
|
281
|
+
if (alreadyClean)
|
|
282
|
+
return;
|
|
283
|
+
values = { ...initial };
|
|
284
|
+
errors = Object.freeze({});
|
|
285
|
+
forceIncludeEmpty.clear();
|
|
286
|
+
invalidate();
|
|
287
|
+
},
|
|
288
|
+
rebase: runRebase,
|
|
289
|
+
setCtx(nextCtx) {
|
|
290
|
+
// Cast back to TCtx: setCtx's public signature takes unknown so
|
|
291
|
+
// callers don't need to plumb generics. If conditions depend on ctx
|
|
292
|
+
// and callers pass the wrong shape, the predicate throws at
|
|
293
|
+
// evaluation time — which is the same fail mode as any other
|
|
294
|
+
// callback contract violation.
|
|
295
|
+
ctx = nextCtx;
|
|
296
|
+
invalidate();
|
|
297
|
+
},
|
|
298
|
+
async submit() {
|
|
299
|
+
const submitCfg = options.submit;
|
|
300
|
+
if (!submitCfg) {
|
|
301
|
+
throw new Error("createFormController: submit() called without a `submit` config. Configure `{ dispatcher, type }` on the controller or drive dispatching manually.");
|
|
302
|
+
}
|
|
303
|
+
// Concurrent-submit guard: a double-click (two invocations before
|
|
304
|
+
// the first network call returned) would otherwise fire two writes
|
|
305
|
+
// AND rebase twice — compounding with the stale-submit race below.
|
|
306
|
+
// Serialize: subsequent calls await the in-flight promise. Same
|
|
307
|
+
// pattern the server-side event-dispatcher uses (inFlightTurns).
|
|
308
|
+
if (submitInFlight)
|
|
309
|
+
return submitInFlight;
|
|
310
|
+
if (!runValidate(submitCfg.validateScope, { includeRoot: true })) {
|
|
311
|
+
return { validationBlocked: true, isSuccess: false };
|
|
312
|
+
}
|
|
313
|
+
const payloadMode = submitCfg.payloadMode ?? "values";
|
|
314
|
+
// Capture the whole snapshot AT submit-time. The user may keep
|
|
315
|
+
// typing while the network call is in flight; on success we rebase
|
|
316
|
+
// ONLY to the values that were actually sent, leaving any
|
|
317
|
+
// subsequent edits as a fresh dirty delta. Without this, an edit
|
|
318
|
+
// during the await would be swallowed into the new baseline and
|
|
319
|
+
// the user would see it as "saved" despite the server never seeing
|
|
320
|
+
// it. Same snapshot is fed to buildPayload — a custom transformer
|
|
321
|
+
// (nested-write case) sees exactly what submit() sees.
|
|
322
|
+
const submittedSnapshot = snapshotStore.getSnapshot();
|
|
323
|
+
const submittedValues = submittedSnapshot.values;
|
|
324
|
+
// buildPayload wins over payloadMode when both are set.
|
|
325
|
+
let payload;
|
|
326
|
+
if (submitCfg.buildPayload) {
|
|
327
|
+
payload = submitCfg.buildPayload(submittedSnapshot);
|
|
328
|
+
}
|
|
329
|
+
else if (payloadMode === "changes") {
|
|
330
|
+
if (submittedSnapshot.isUnchanged) {
|
|
331
|
+
return {
|
|
332
|
+
validationBlocked: false,
|
|
333
|
+
isSuccess: true,
|
|
334
|
+
isNoOp: true,
|
|
335
|
+
data: submittedValues,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
payload = submittedSnapshot.changes;
|
|
339
|
+
}
|
|
340
|
+
else if (submitCfg.stripEmptySeeds === false) {
|
|
341
|
+
payload = submittedValues;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
payload = stripUntouchedEmptyStrings(submittedValues, submittedSnapshot.initial, forceIncludeEmpty);
|
|
345
|
+
}
|
|
346
|
+
payload = omitPayloadFields(payload, submitCfg.omitFields);
|
|
347
|
+
// Only enforced here, right before the write actually happens — a
|
|
348
|
+
// form that never reaches this point (blocked by validation, or a
|
|
349
|
+
// clean payloadMode "changes" no-op) must not throw just because no
|
|
350
|
+
// dispatcher was ever wired up.
|
|
351
|
+
const dispatcher = submitCfg.dispatcher;
|
|
352
|
+
if (!dispatcher) {
|
|
353
|
+
// Return SubmitResult (not throw) so RenderEdit's banner path shows
|
|
354
|
+
// the misconfiguration instead of an unhandled rejection on save.
|
|
355
|
+
return {
|
|
356
|
+
validationBlocked: false,
|
|
357
|
+
isSuccess: false,
|
|
358
|
+
isNoOp: false,
|
|
359
|
+
error: {
|
|
360
|
+
code: "misconfigured",
|
|
361
|
+
httpStatus: 500,
|
|
362
|
+
i18nKey: "errors.internal",
|
|
363
|
+
message: "createFormController: submit() called without a dispatcher (dispatcher was absent when the form mounted). Pass `submit.dispatcher` explicitly, or mount a <DispatcherProvider> above this form.",
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
const runWrite = async () => {
|
|
368
|
+
const result = await dispatcher.write(submitCfg.type, payload);
|
|
369
|
+
if (result.isSuccess) {
|
|
370
|
+
// Remember intentional empty clears so post-rebase submits keep them (#1858).
|
|
371
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
372
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
373
|
+
if (v === "")
|
|
374
|
+
forceIncludeEmpty.add(k);
|
|
375
|
+
else
|
|
376
|
+
forceIncludeEmpty.delete(k);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Rebase to the SNAPSHOT, not to the current values — see
|
|
380
|
+
// runRebaseToSnapshot comment for why.
|
|
381
|
+
runRebaseToSnapshot(submittedValues);
|
|
382
|
+
return { validationBlocked: false, isSuccess: true, isNoOp: false, data: result.data };
|
|
383
|
+
}
|
|
384
|
+
const serverFields = result.error.details?.fields;
|
|
385
|
+
if (serverFields && serverFields.length > 0) {
|
|
386
|
+
const prefix = submitCfg.serverFieldPathPrefix;
|
|
387
|
+
const mappedFields = prefix
|
|
388
|
+
? stripServerFieldPathPrefix(serverFields, prefix)
|
|
389
|
+
: serverFields;
|
|
390
|
+
errors = Object.freeze(groupIssuesByPath(mappedFields));
|
|
391
|
+
invalidate();
|
|
392
|
+
const error = prefix
|
|
393
|
+
? { ...result.error, details: { ...result.error.details, fields: mappedFields } }
|
|
394
|
+
: result.error;
|
|
395
|
+
return { validationBlocked: false, isSuccess: false, isNoOp: false, error };
|
|
396
|
+
}
|
|
397
|
+
return { validationBlocked: false, isSuccess: false, isNoOp: false, error: result.error };
|
|
398
|
+
};
|
|
399
|
+
const writeInFlight = runWrite();
|
|
400
|
+
submitInFlight = writeInFlight;
|
|
401
|
+
try {
|
|
402
|
+
return await writeInFlight;
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
submitInFlight = null;
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createFormController } from "./form-controller.ts";
|
|
2
|
+
export type { FieldConditionPredicate, FieldConditions, FieldConditionValue, FieldState, FormController, FormControllerOptions, FormSnapshot, FormValues, SubmitConfig, SubmitPayloadMode, SubmitResult, } from "./types.ts";
|
|
3
|
+
export { groupIssuesByPath, I18N_KEY_PARAM, relevantFieldIssues, zodErrorToFieldIssues, } from "./zod-bridge.ts";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ZodType } from "zod";
|
|
2
|
+
import type { Dispatcher, FieldIssue, WriteResult } from "../dispatcher/index.ts";
|
|
3
|
+
export type FormValues = Record<string, unknown>;
|
|
4
|
+
export type FormScopeKey<TValues extends FormValues> = (keyof TValues & string) | `${keyof TValues & string}.${string}`;
|
|
5
|
+
export type FieldConditionPredicate<TValues extends FormValues, TCtx> = (values: TValues, ctx: TCtx) => boolean;
|
|
6
|
+
export type FieldConditionValue<TValues extends FormValues, TCtx> = boolean | FieldConditionPredicate<TValues, TCtx>;
|
|
7
|
+
export type FieldConditions<TValues extends FormValues, TCtx = unknown> = {
|
|
8
|
+
readonly visible?: FieldConditionValue<TValues, TCtx>;
|
|
9
|
+
readonly readonly?: FieldConditionValue<TValues, TCtx>;
|
|
10
|
+
readonly required?: FieldConditionValue<TValues, TCtx>;
|
|
11
|
+
};
|
|
12
|
+
export type FieldState = {
|
|
13
|
+
readonly visible: boolean;
|
|
14
|
+
readonly readonly: boolean;
|
|
15
|
+
readonly required: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type FormSnapshot<TValues extends FormValues> = {
|
|
18
|
+
readonly values: TValues;
|
|
19
|
+
readonly initial: TValues;
|
|
20
|
+
readonly changes: Partial<TValues>;
|
|
21
|
+
readonly isDirty: boolean;
|
|
22
|
+
readonly isUnchanged: boolean;
|
|
23
|
+
readonly errors: Readonly<Record<string, readonly FieldIssue[]>>;
|
|
24
|
+
readonly fields: Readonly<Record<string, FieldState>>;
|
|
25
|
+
};
|
|
26
|
+
export type FormController<TValues extends FormValues> = {
|
|
27
|
+
getSnapshot(): FormSnapshot<TValues>;
|
|
28
|
+
subscribe(listener: () => void): () => void;
|
|
29
|
+
setField<K extends keyof TValues>(key: K, value: TValues[K]): void;
|
|
30
|
+
setValues(partial: Partial<TValues>): void;
|
|
31
|
+
clearErrors(path?: string): void;
|
|
32
|
+
setErrors(errors: Readonly<Record<string, readonly FieldIssue[]>>): void;
|
|
33
|
+
validate(scope?: readonly FormScopeKey<TValues>[]): boolean;
|
|
34
|
+
reset(): void;
|
|
35
|
+
rebase(): void;
|
|
36
|
+
setCtx(ctx: unknown): void;
|
|
37
|
+
submit<TData = unknown>(): Promise<SubmitResult<TData>>;
|
|
38
|
+
};
|
|
39
|
+
export type FormControllerOptions<TValues extends FormValues, TCtx = unknown> = {
|
|
40
|
+
readonly initial: TValues;
|
|
41
|
+
readonly schema?: ZodType;
|
|
42
|
+
readonly fields?: Readonly<Record<string, FieldConditions<TValues, TCtx>>>;
|
|
43
|
+
readonly ctx?: TCtx;
|
|
44
|
+
readonly submit?: SubmitConfig<TValues>;
|
|
45
|
+
};
|
|
46
|
+
export type SubmitPayloadMode = "values" | "changes";
|
|
47
|
+
export type SubmitConfig<TValues extends FormValues = FormValues> = {
|
|
48
|
+
readonly dispatcher?: Dispatcher;
|
|
49
|
+
readonly type: string;
|
|
50
|
+
readonly payloadMode?: SubmitPayloadMode;
|
|
51
|
+
/** Only applies to payloadMode "values". Controls whether untouched
|
|
52
|
+
* empty-string fields (still equal to their initial "" seed) are
|
|
53
|
+
* stripped from the payload before dispatch — see SubmitPayloadMode's
|
|
54
|
+
* "values" doc for why the stripping exists. Default `true` (current
|
|
55
|
+
* behavior). Set `false` to send those fields as `""` unchanged. */
|
|
56
|
+
readonly stripEmptySeeds?: boolean;
|
|
57
|
+
/** Top-level payload keys removed right before dispatch, for fields that
|
|
58
|
+
* are shown or validated but must not reach the write handler. Applied
|
|
59
|
+
* to the built payload only when it is a plain object. */
|
|
60
|
+
readonly omitFields?: readonly string[];
|
|
61
|
+
readonly buildPayload?: (snapshot: FormSnapshot<TValues>) => unknown;
|
|
62
|
+
readonly serverFieldPathPrefix?: string;
|
|
63
|
+
/** Restricts submit()'s internal validate() call to these field names —
|
|
64
|
+
* same contract as validate()'s `scope` param. Set by RenderEdit when the
|
|
65
|
+
* caller's `fields` prop narrows which fields are rendered, so an
|
|
66
|
+
* unrendered required field can't block a submit the user can't fix.
|
|
67
|
+
* Omitted = unscoped (existing behavior, validates everything). */
|
|
68
|
+
readonly validateScope?: readonly string[];
|
|
69
|
+
};
|
|
70
|
+
export type SubmitResult<TData = unknown> = ({
|
|
71
|
+
readonly validationBlocked: false;
|
|
72
|
+
readonly isNoOp?: boolean;
|
|
73
|
+
} & WriteResult<TData>) | {
|
|
74
|
+
readonly validationBlocked: true;
|
|
75
|
+
readonly isSuccess: false;
|
|
76
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ZodError } from "zod";
|
|
2
|
+
import type { FieldIssue } from "../dispatcher/index.ts";
|
|
3
|
+
export declare function zodErrorToFieldIssues(error: ZodError): FieldIssue[];
|
|
4
|
+
export declare function relevantFieldIssues(issues: readonly FieldIssue[], opts: {
|
|
5
|
+
readonly hiddenFields: ReadonlySet<string>;
|
|
6
|
+
readonly scope?: readonly string[];
|
|
7
|
+
readonly includeRoot?: boolean;
|
|
8
|
+
}): FieldIssue[];
|
|
9
|
+
export declare const I18N_KEY_PARAM = "i18nKey";
|
|
10
|
+
export declare function groupIssuesByPath(issues: readonly FieldIssue[]): Record<string, readonly FieldIssue[]>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Translates a ZodError into the same FieldIssue shape the server emits via
|
|
2
|
+
// its own zod-bridge (packages/framework/src/errors/zod-bridge.ts). The two
|
|
3
|
+
// bridges stay in sync on purpose: a field failing zod on the client reads
|
|
4
|
+
// identically to the same field failing zod on the server, so the UI's
|
|
5
|
+
// error-display path doesn't branch on provenance.
|
|
6
|
+
//
|
|
7
|
+
// Duplicated here (not imported) so ui-core remains free of
|
|
8
|
+
// @cosmicdrift/kumiko-framework — this package has to bundle for browsers and Expo,
|
|
9
|
+
// where a server-oriented framework dep would balloon bundle size and drag
|
|
10
|
+
// in Node-only modules (postgres, drizzle, bullmq) even if only types are
|
|
11
|
+
// consumed.
|
|
12
|
+
//
|
|
13
|
+
// Keep this list in sync with the server-side mirror — Zod version bumps
|
|
14
|
+
// tend to introduce new param keys, and the server's classes.test.ts is
|
|
15
|
+
// what catches them. A value added there should be added here too.
|
|
16
|
+
const ISSUE_PARAM_KEYS = [
|
|
17
|
+
"minimum",
|
|
18
|
+
"maximum",
|
|
19
|
+
"expected",
|
|
20
|
+
"received",
|
|
21
|
+
"type",
|
|
22
|
+
"inclusive",
|
|
23
|
+
"exact",
|
|
24
|
+
"keys",
|
|
25
|
+
// Zod 4 additions
|
|
26
|
+
"format",
|
|
27
|
+
"divisor",
|
|
28
|
+
"values",
|
|
29
|
+
"pattern",
|
|
30
|
+
];
|
|
31
|
+
// Flat FieldIssue list — same shape as server-side ValidationError.fields.
|
|
32
|
+
// The form-controller groups this by `path` for its snapshot's errors map.
|
|
33
|
+
export function zodErrorToFieldIssues(error) {
|
|
34
|
+
return error.issues.map((issue) => {
|
|
35
|
+
const params = extractIssueParams(issue);
|
|
36
|
+
return {
|
|
37
|
+
// Empty-path issues (top-level object/array failures) use "(root)" so
|
|
38
|
+
// the form-controller has a stable key to show "form itself is
|
|
39
|
+
// malformed" errors under — matches what the server does.
|
|
40
|
+
path: issue.path.map(String).join(".") || "(root)",
|
|
41
|
+
code: issue.code,
|
|
42
|
+
i18nKey: resolveI18nKey(issue),
|
|
43
|
+
...(params && { params }),
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// The issues that count for validity: hidden fields never block, and a
|
|
48
|
+
// scope narrows to the listed paths (an entry matches itself and everything
|
|
49
|
+
// below it, not siblings — #1898). `includeRoot` keeps "(root)" refine issues
|
|
50
|
+
// inside a scope (submit passes it, #1907). Shared by the controller's
|
|
51
|
+
// validate() and RenderEdit's `valid` dry-run so the two cannot drift.
|
|
52
|
+
export function relevantFieldIssues(issues, opts) {
|
|
53
|
+
const { hiddenFields, scope, includeRoot } = opts;
|
|
54
|
+
return issues.filter((issue) => {
|
|
55
|
+
const rootField = issue.path.split(".")[0] ?? "";
|
|
56
|
+
if (hiddenFields.has(rootField))
|
|
57
|
+
return false;
|
|
58
|
+
if (scope === undefined)
|
|
59
|
+
return true;
|
|
60
|
+
if (includeRoot === true && issue.path === "(root)")
|
|
61
|
+
return true;
|
|
62
|
+
return scope.some((entry) => issue.path === entry || issue.path.startsWith(`${entry}.`));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Same magic key both sides of a superRefine can set to opt out of the
|
|
66
|
+
// generic `errors.validation.custom` message — see form-schema.ts.
|
|
67
|
+
export const I18N_KEY_PARAM = "i18nKey";
|
|
68
|
+
// `code: "custom"` is zod's catch-all for every superRefine/refine check;
|
|
69
|
+
// left mechanical it'd collapse onto one generic key, so a superRefine can
|
|
70
|
+
// set `params[I18N_KEY_PARAM]` to override it. Keep in sync with the server
|
|
71
|
+
// mirror (packages/framework/src/errors/zod-bridge.ts).
|
|
72
|
+
function resolveI18nKey(issue) {
|
|
73
|
+
if (issue.code === "custom") {
|
|
74
|
+
const override = issue.params?.[I18N_KEY_PARAM];
|
|
75
|
+
if (typeof override === "string")
|
|
76
|
+
return override;
|
|
77
|
+
}
|
|
78
|
+
return `errors.validation.${issue.code}`;
|
|
79
|
+
}
|
|
80
|
+
function extractIssueParams(issue) {
|
|
81
|
+
const out = {};
|
|
82
|
+
const bag = issue; // @cast-boundary zod-issue
|
|
83
|
+
for (const key of ISSUE_PARAM_KEYS) {
|
|
84
|
+
if (bag[key] !== undefined)
|
|
85
|
+
out[key] = bag[key];
|
|
86
|
+
}
|
|
87
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
88
|
+
}
|
|
89
|
+
// Groups a flat FieldIssue list into the `errors` map carried by a
|
|
90
|
+
// FormSnapshot. A field with multiple issues (e.g. both "required" and
|
|
91
|
+
// "min") keeps both — UIs that only render the first can pick the first
|
|
92
|
+
// element, those that want a full list have it.
|
|
93
|
+
export function groupIssuesByPath(issues) {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const issue of issues) {
|
|
96
|
+
const bucket = out[issue.path];
|
|
97
|
+
if (bucket)
|
|
98
|
+
bucket.push(issue);
|
|
99
|
+
else
|
|
100
|
+
out[issue.path] = [issue];
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const DOCUMENT_FALLBACK_CURRENCY = "EUR";
|
|
2
|
+
export declare function formatDocumentMoney(value: unknown, tenantCurrency: string, locale: string): string;
|
|
3
|
+
export type DocumentDateOptions = {
|
|
4
|
+
/** Append the time of day. Only instants and offset date-times carry one; a
|
|
5
|
+
* date-only value prints just the date. */
|
|
6
|
+
readonly time?: boolean;
|
|
7
|
+
/** IANA zone an instant is shown in (default UTC). Offset date-times keep
|
|
8
|
+
* their own wall clock. */
|
|
9
|
+
readonly timeZone?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function formatDocumentDate(value: unknown, locale: string, options?: DocumentDateOptions): string;
|
|
12
|
+
export declare function formatDocumentPercent(value: unknown, locale: string): string;
|