@cosmicdrift/kumiko-renderer 0.388.0 → 0.390.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/dist/app/base-path-context.d.ts +9 -0
- package/dist/app/base-path-context.js +13 -0
- package/dist/app/base-path.d.ts +3 -0
- package/dist/app/base-path.js +27 -0
- package/dist/app/config-edit-body.d.ts +1 -1
- package/dist/app/config-edit-body.js +15 -6
- package/dist/app/config-edit-without-secret-fields.d.ts +2 -0
- package/dist/app/config-edit-without-secret-fields.js +28 -0
- package/dist/app/entity-list-facet-specs.d.ts +3 -0
- package/dist/app/entity-list-facet-specs.js +65 -0
- package/dist/app/entity-list-screen.js +74 -206
- package/dist/app/kumiko-screen.js +6 -14
- package/dist/app/list-bulk-actions.d.ts +18 -0
- package/dist/app/list-bulk-actions.js +54 -0
- package/dist/app/locked-screen-header.d.ts +9 -0
- package/dist/app/locked-screen-header.js +33 -0
- package/dist/app/projection-detail-body.js +59 -394
- package/dist/app/projection-detail-header-model.d.ts +11 -0
- package/dist/app/projection-detail-header-model.js +33 -0
- package/dist/app/projection-detail-header-region.d.ts +28 -0
- package/dist/app/projection-detail-header-region.js +81 -0
- package/dist/app/projection-detail-header.d.ts +29 -0
- package/dist/app/projection-detail-header.js +84 -0
- package/dist/app/projection-detail-navigation.d.ts +13 -0
- package/dist/app/projection-detail-navigation.js +91 -0
- package/dist/app/projection-detail-states.d.ts +22 -0
- package/dist/app/projection-detail-states.js +26 -0
- package/dist/app/projection-drawer-host.d.ts +12 -0
- package/dist/app/projection-drawer-host.js +10 -0
- package/dist/app/projection-list-body.d.ts +1 -1
- package/dist/app/projection-list-body.js +82 -197
- package/dist/app/projection-list-render-props.d.ts +56 -0
- package/dist/app/projection-list-render-props.js +47 -0
- package/dist/app/projection-list-render.d.ts +26 -0
- package/dist/app/projection-list-render.js +20 -0
- package/dist/app/projection-list-row-click.d.ts +12 -0
- package/dist/app/projection-list-row-click.js +14 -0
- package/dist/app/row-actions.d.ts +3 -0
- package/dist/app/row-actions.js +10 -5
- package/dist/app/screen-shared.js +9 -2
- package/dist/app/use-entity-range-facets.d.ts +24 -0
- package/dist/app/use-entity-range-facets.js +64 -0
- package/dist/app/use-projection-date-range.d.ts +12 -0
- package/dist/app/use-projection-date-range.js +21 -0
- package/dist/app/use-projection-detail-actions.d.ts +35 -0
- package/dist/app/use-projection-detail-actions.js +156 -0
- package/dist/app/use-projection-detail-data.d.ts +19 -0
- package/dist/app/use-projection-detail-data.js +47 -0
- package/dist/app/use-projection-detail-layout.d.ts +17 -0
- package/dist/app/use-projection-detail-layout.js +23 -0
- package/dist/app/use-projection-list-actions.d.ts +31 -0
- package/dist/app/use-projection-list-actions.js +73 -0
- package/dist/app/use-projection-list-data.d.ts +20 -0
- package/dist/app/use-projection-list-data.js +26 -0
- package/dist/app/use-projection-list-definitions.d.ts +11 -0
- package/dist/app/use-projection-list-definitions.js +20 -0
- package/dist/app/use-projection-list-query.d.ts +28 -0
- package/dist/app/use-projection-list-query.js +83 -0
- package/dist/app/use-screen-list-rows.d.ts +37 -0
- package/dist/app/use-screen-list-rows.js +104 -0
- package/dist/app/use-screen-list-table.d.ts +24 -0
- package/dist/app/use-screen-list-table.js +46 -0
- package/dist/components/list-group-page-bridges.d.ts +3 -0
- package/dist/components/list-group-page-bridges.js +31 -0
- package/dist/components/list-state-store.d.ts +16 -0
- package/dist/components/list-state-store.js +31 -0
- package/dist/components/related-list-section.js +37 -89
- package/dist/components/render-edit-banners.d.ts +12 -0
- package/dist/components/render-edit-banners.js +90 -0
- package/dist/components/render-edit-draft-save-helpers.d.ts +19 -0
- package/dist/components/render-edit-draft-save-helpers.js +42 -0
- package/dist/components/render-edit-field-grid.d.ts +4 -0
- package/dist/components/render-edit-field-grid.js +40 -0
- package/dist/components/render-edit-fields-section.d.ts +4 -0
- package/dist/components/render-edit-fields-section.js +76 -0
- package/dist/components/render-edit-form-actions.d.ts +4 -0
- package/dist/components/render-edit-form-actions.js +66 -0
- package/dist/components/render-edit-header-actions.d.ts +11 -0
- package/dist/components/render-edit-header-actions.js +120 -0
- package/dist/components/render-edit-layout-flags.d.ts +29 -0
- package/dist/components/render-edit-layout-flags.js +106 -0
- package/dist/components/render-edit-layout.d.ts +4 -0
- package/dist/components/render-edit-layout.js +79 -0
- package/dist/components/render-edit-render-model.d.ts +63 -0
- package/dist/components/render-edit-render-model.js +14 -0
- package/dist/components/render-edit-section-kinds.d.ts +4 -0
- package/dist/components/render-edit-section-kinds.js +115 -0
- package/dist/components/render-edit-submit-result.d.ts +8 -0
- package/dist/components/render-edit-submit-result.js +77 -0
- package/dist/components/render-edit-submit.d.ts +7 -0
- package/dist/components/render-edit-submit.js +113 -0
- package/dist/components/render-edit-titles.d.ts +17 -0
- package/dist/components/render-edit-titles.js +99 -0
- package/dist/components/render-edit-wizard-navigation.d.ts +11 -0
- package/dist/components/render-edit-wizard-navigation.js +115 -0
- package/dist/components/render-edit.d.ts +2 -2
- package/dist/components/render-edit.js +63 -1128
- package/dist/components/render-field-choice-inputs.d.ts +7 -0
- package/dist/components/render-field-choice-inputs.js +52 -0
- package/dist/components/render-field-control.d.ts +12 -0
- package/dist/components/render-field-control.js +93 -0
- package/dist/components/render-field-display-text.d.ts +4 -0
- package/dist/components/render-field-display-text.js +48 -0
- package/dist/components/render-field-fill-action.d.ts +13 -0
- package/dist/components/render-field-fill-action.js +63 -0
- package/dist/components/render-field-input-shared.d.ts +39 -0
- package/dist/components/render-field-input-shared.js +20 -0
- package/dist/components/render-field-input.d.ts +3 -0
- package/dist/components/render-field-input.js +34 -0
- package/dist/components/render-field-meta.d.ts +6 -0
- package/dist/components/render-field-meta.js +51 -0
- package/dist/components/render-field-read-only.d.ts +11 -0
- package/dist/components/render-field-read-only.js +75 -0
- package/dist/components/render-field-reference.d.ts +15 -0
- package/dist/components/render-field-reference.js +103 -0
- package/dist/components/render-field-scalar-inputs.d.ts +10 -0
- package/dist/components/render-field-scalar-inputs.js +35 -0
- package/dist/components/render-field-text-inputs.d.ts +3 -0
- package/dist/components/render-field-text-inputs.js +34 -0
- package/dist/components/render-field-values.d.ts +18 -0
- package/dist/components/render-field-values.js +111 -0
- package/dist/components/render-field-write-only.d.ts +9 -0
- package/dist/components/render-field-write-only.js +29 -0
- package/dist/components/render-field.d.ts +2 -2
- package/dist/components/render-field.js +39 -669
- package/dist/components/render-list-header-slot.d.ts +5 -0
- package/dist/components/render-list-header-slot.js +30 -0
- package/dist/components/render-list-reference-bridge.d.ts +3 -0
- package/dist/components/render-list-reference-bridge.js +25 -0
- package/dist/components/render-list-reference-columns.d.ts +25 -0
- package/dist/components/render-list-reference-columns.js +137 -0
- package/dist/components/render-list-table-props.d.ts +52 -0
- package/dist/components/render-list-table-props.js +53 -0
- package/dist/components/render-list-toolbar-action.d.ts +27 -0
- package/dist/components/render-list-toolbar-action.js +40 -0
- package/dist/components/render-list-toolbar-model.d.ts +42 -0
- package/dist/components/render-list-toolbar-model.js +82 -0
- package/dist/components/render-list-toolbar.d.ts +38 -0
- package/dist/components/render-list-toolbar.js +40 -0
- package/dist/components/render-list-types.d.ts +113 -0
- package/dist/components/render-list-types.js +1 -0
- package/dist/components/render-list.d.ts +5 -130
- package/dist/components/render-list.js +95 -319
- package/dist/components/use-reference-input-state.d.ts +20 -0
- package/dist/components/use-reference-input-state.js +85 -0
- package/dist/components/use-render-edit-controlled-sync.d.ts +3 -0
- package/dist/components/use-render-edit-controlled-sync.js +26 -0
- package/dist/components/use-render-edit-draft-save.d.ts +29 -0
- package/dist/components/use-render-edit-draft-save.js +141 -0
- package/dist/components/use-render-edit-form.d.ts +10 -0
- package/dist/components/use-render-edit-form.js +101 -0
- package/dist/components/use-render-edit-sections.d.ts +15 -0
- package/dist/components/use-render-edit-sections.js +100 -0
- package/dist/components/use-render-edit-state.d.ts +65 -0
- package/dist/components/use-render-edit-state.js +158 -0
- package/dist/components/use-render-edit-steps.d.ts +13 -0
- package/dist/components/use-render-edit-steps.js +52 -0
- package/dist/components/use-render-list-columns.d.ts +18 -0
- package/dist/components/use-render-list-columns.js +41 -0
- package/dist/components/use-render-list-search.d.ts +4 -0
- package/dist/components/use-render-list-search.js +50 -0
- package/dist/current-user.d.ts +2 -0
- package/dist/data-table-aggregate-types.d.ts +47 -0
- package/dist/data-table-aggregate-types.js +1 -0
- package/dist/data-table-bulk-types.d.ts +27 -0
- package/dist/data-table-bulk-types.js +1 -0
- package/dist/data-table-facet-types.d.ts +36 -0
- package/dist/data-table-facet-types.js +1 -0
- package/dist/data-table-view-types.d.ts +73 -0
- package/dist/data-table-view-types.js +1 -0
- package/dist/headless/view-model/edit.js +3 -1
- package/dist/headless/view-model/types.d.ts +4 -2
- package/dist/hooks/list-column-layout.d.ts +19 -0
- package/dist/hooks/list-column-layout.js +36 -0
- package/dist/hooks/list-group-pages.d.ts +26 -0
- package/dist/hooks/list-group-pages.js +37 -0
- package/dist/hooks/list-reset.d.ts +8 -0
- package/dist/hooks/list-reset.js +15 -0
- package/dist/hooks/list-server-groups.d.ts +39 -0
- package/dist/hooks/list-server-groups.js +84 -0
- package/dist/hooks/list-state-schema.d.ts +28 -0
- package/dist/hooks/list-state-schema.js +76 -0
- package/dist/hooks/use-bulk-run.d.ts +41 -0
- package/dist/hooks/use-bulk-run.js +65 -0
- package/dist/hooks/use-group-load-state.d.ts +11 -0
- package/dist/hooks/use-group-load-state.js +31 -0
- package/dist/hooks/use-list-aggregates.d.ts +38 -0
- package/dist/hooks/use-list-aggregates.js +107 -0
- package/dist/hooks/use-list-table.d.ts +38 -0
- package/dist/hooks/use-list-table.js +91 -0
- package/dist/hooks/use-list-view-state.d.ts +43 -0
- package/dist/hooks/use-list-view-state.js +161 -0
- package/dist/hooks/use-related-list-rows.d.ts +27 -0
- package/dist/hooks/use-related-list-rows.js +81 -0
- package/dist/hooks/use-related-list-state.d.ts +15 -0
- package/dist/hooks/use-related-list-state.js +31 -0
- package/dist/hooks/use-remembered-list-params.d.ts +13 -0
- package/dist/hooks/use-remembered-list-params.js +53 -0
- package/dist/hooks/use-remembered-local-list-state.d.ts +26 -0
- package/dist/hooks/use-remembered-local-list-state.js +63 -0
- package/dist/i18n-defaults.js +53 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/primitives.d.ts +11 -47
- package/dist/web/app/create-app.d.ts +5 -0
- package/dist/web/app/create-app.js +7 -4
- package/dist/web/app/dashboard-body.d.ts +1 -1
- package/dist/web/app/dashboard-body.js +42 -246
- package/dist/web/app/dashboard-header.d.ts +34 -0
- package/dist/web/app/dashboard-header.js +58 -0
- package/dist/web/app/dashboard-screen-controls.d.ts +11 -0
- package/dist/web/app/dashboard-screen-controls.js +42 -0
- package/dist/web/app/dashboard-screen-tiles.d.ts +8 -0
- package/dist/web/app/dashboard-screen-tiles.js +42 -0
- package/dist/web/app/dashboard-visibility.d.ts +42 -0
- package/dist/web/app/dashboard-visibility.js +102 -0
- package/dist/web/app/history-leave-guard.d.ts +6 -0
- package/dist/web/app/history-leave-guard.js +115 -0
- package/dist/web/app/list-state-store-boot.d.ts +6 -0
- package/dist/web/app/list-state-store-boot.js +15 -0
- package/dist/web/app/nav.js +7 -54
- package/dist/web/app/session-list-state-store.d.ts +15 -0
- package/dist/web/app/session-list-state-store.js +117 -0
- package/dist/web/app/use-dashboard-screen-state.d.ts +35 -0
- package/dist/web/app/use-dashboard-screen-state.js +69 -0
- package/dist/web/index.d.ts +1 -0
- package/dist/web/index.js +1 -0
- package/dist/web/lib/export-xlsx-request.d.ts +11 -0
- package/dist/web/lib/export-xlsx-request.js +46 -0
- package/dist/web/primitives/data-table/actions-column.d.ts +16 -0
- package/dist/web/primitives/data-table/actions-column.js +35 -0
- package/dist/web/primitives/data-table/bulk-selection-bar.d.ts +20 -0
- package/dist/web/primitives/data-table/bulk-selection-bar.js +37 -0
- package/dist/web/primitives/data-table/card-body.d.ts +6 -0
- package/dist/web/primitives/data-table/card-body.js +32 -0
- package/dist/web/primitives/data-table/card-row.d.ts +5 -0
- package/dist/web/primitives/data-table/card-row.js +31 -0
- package/dist/web/primitives/data-table/card-sort-select.d.ts +14 -0
- package/dist/web/primitives/data-table/card-sort-select.js +40 -0
- package/dist/web/primitives/data-table/card-view.d.ts +3 -0
- package/dist/web/primitives/data-table/card-view.js +13 -0
- package/dist/web/primitives/data-table/column-layout-mode.d.ts +5 -0
- package/dist/web/primitives/data-table/column-layout-mode.js +10 -0
- package/dist/web/primitives/data-table/column-picker-section.d.ts +7 -0
- package/dist/web/primitives/data-table/column-picker-section.js +35 -0
- package/dist/web/primitives/data-table/column-resize-handle.d.ts +14 -0
- package/dist/web/primitives/data-table/column-resize-handle.js +48 -0
- package/dist/web/primitives/data-table/data-cell-props.d.ts +7 -0
- package/dist/web/primitives/data-table/data-cell-props.js +8 -0
- package/dist/web/primitives/data-table/density.d.ts +14 -0
- package/dist/web/primitives/data-table/density.js +20 -0
- package/dist/web/primitives/data-table/export-button.d.ts +5 -0
- package/dist/web/primitives/data-table/export-button.js +37 -0
- package/dist/web/primitives/data-table/facet-cluster.d.ts +8 -0
- package/dist/web/primitives/data-table/facet-cluster.js +37 -0
- package/dist/web/primitives/data-table/group-band.d.ts +28 -0
- package/dist/web/primitives/data-table/group-band.js +53 -0
- package/dist/web/primitives/data-table/measure-value.d.ts +10 -0
- package/dist/web/primitives/data-table/measure-value.js +28 -0
- package/dist/web/primitives/data-table/pin-section.d.ts +8 -0
- package/dist/web/primitives/data-table/pin-section.js +20 -0
- package/dist/web/primitives/data-table/reset-all-button.d.ts +4 -0
- package/dist/web/primitives/data-table/reset-all-button.js +8 -0
- package/dist/web/primitives/data-table/row-render-parts.d.ts +8 -0
- package/dist/web/primitives/data-table/row-render-parts.js +35 -0
- package/dist/web/primitives/data-table/server-group-header.d.ts +11 -0
- package/dist/web/primitives/data-table/server-group-header.js +51 -0
- package/dist/web/primitives/data-table/sortable-header.d.ts +17 -0
- package/dist/web/primitives/data-table/sortable-header.js +41 -0
- package/dist/web/primitives/data-table/table-body.d.ts +3 -0
- package/dist/web/primitives/data-table/table-body.js +32 -0
- package/dist/web/primitives/data-table/table-chrome.d.ts +13 -0
- package/dist/web/primitives/data-table/table-chrome.js +30 -0
- package/dist/web/primitives/data-table/table-content.d.ts +3 -0
- package/dist/web/primitives/data-table/table-content.js +41 -0
- package/dist/web/primitives/data-table/table-data-cell.d.ts +4 -0
- package/dist/web/primitives/data-table/table-data-cell.js +25 -0
- package/dist/web/primitives/data-table/table-footer.d.ts +7 -0
- package/dist/web/primitives/data-table/table-footer.js +20 -0
- package/dist/web/primitives/data-table/table-header-row.d.ts +3 -0
- package/dist/web/primitives/data-table/table-header-row.js +21 -0
- package/dist/web/primitives/data-table/table-view.d.ts +3 -0
- package/dist/web/primitives/data-table/table-view.js +25 -0
- package/dist/web/primitives/data-table/toolbar-end-cluster.d.ts +10 -0
- package/dist/web/primitives/data-table/toolbar-end-cluster.js +13 -0
- package/dist/web/primitives/data-table/toolbar.d.ts +6 -0
- package/dist/web/primitives/data-table/toolbar.js +53 -0
- package/dist/web/primitives/data-table/totals-footer.d.ts +24 -0
- package/dist/web/primitives/data-table/totals-footer.js +48 -0
- package/dist/web/primitives/data-table/use-bulk-selection.d.ts +21 -0
- package/dist/web/primitives/data-table/use-bulk-selection.js +66 -0
- package/dist/web/primitives/data-table/use-column-pins.d.ts +32 -0
- package/dist/web/primitives/data-table/use-column-pins.js +117 -0
- package/dist/web/primitives/data-table/use-column-sizing.d.ts +22 -0
- package/dist/web/primitives/data-table/use-column-sizing.js +44 -0
- package/dist/web/primitives/data-table/use-data-table-state.d.ts +44 -0
- package/dist/web/primitives/data-table/use-data-table-state.js +123 -0
- package/dist/web/primitives/data-table/use-measured-column-widths.d.ts +15 -0
- package/dist/web/primitives/data-table/use-measured-column-widths.js +83 -0
- package/dist/web/primitives/data-table/use-row-keyboard-navigation.d.ts +22 -0
- package/dist/web/primitives/data-table/use-row-keyboard-navigation.js +65 -0
- package/dist/web/primitives/data-table/use-row-selection.d.ts +14 -0
- package/dist/web/primitives/data-table/use-row-selection.js +48 -0
- package/dist/web/primitives/data-table/use-view-columns.d.ts +6 -0
- package/dist/web/primitives/data-table/use-view-columns.js +17 -0
- package/dist/web/primitives/data-table/view-menu.d.ts +8 -0
- package/dist/web/primitives/data-table/view-menu.js +45 -0
- package/dist/web/primitives/data-table-cell.d.ts +0 -22
- package/dist/web/primitives/data-table-cell.js +1 -58
- package/dist/web/primitives/data-table.d.ts +3 -4
- package/dist/web/primitives/data-table.js +30 -272
- package/dist/web/primitives/file-value-list.d.ts +1 -1
- package/dist/web/primitives/file-value-list.js +5 -5
- package/dist/web/primitives/form-footer-parts.d.ts +25 -0
- package/dist/web/primitives/form-footer-parts.js +55 -0
- package/dist/web/primitives/form.js +33 -43
- package/dist/web/primitives/input-choice-kinds.d.ts +4 -0
- package/dist/web/primitives/input-choice-kinds.js +67 -0
- package/dist/web/primitives/input-file-kinds.d.ts +3 -0
- package/dist/web/primitives/input-file-kinds.js +6 -0
- package/dist/web/primitives/input-number-kinds.d.ts +4 -0
- package/dist/web/primitives/input-number-kinds.js +16 -0
- package/dist/web/primitives/input-optional-props.d.ts +20 -0
- package/dist/web/primitives/input-optional-props.js +24 -0
- package/dist/web/primitives/input-select-presentation.d.ts +13 -0
- package/dist/web/primitives/input-select-presentation.js +19 -0
- package/dist/web/primitives/input-shared.d.ts +22 -0
- package/dist/web/primitives/input-shared.js +10 -0
- package/dist/web/primitives/input-temporal-kinds.d.ts +5 -0
- package/dist/web/primitives/input-temporal-kinds.js +10 -0
- package/dist/web/primitives/input-text-kinds.d.ts +7 -0
- package/dist/web/primitives/input-text-kinds.js +33 -0
- package/dist/web/primitives/input.js +35 -161
- package/dist/web/primitives/number-range-facet.d.ts +7 -0
- package/dist/web/primitives/number-range-facet.js +33 -0
- package/dist/web/widgets/bounded-load-cache.d.ts +5 -1
- package/dist/web/widgets/bounded-load-cache.js +48 -26
- package/dist/web/widgets/chart-data.d.ts +35 -0
- package/dist/web/widgets/chart-data.js +37 -0
- package/dist/web/widgets/chart-markers.d.ts +26 -0
- package/dist/web/widgets/chart-markers.js +64 -0
- package/dist/web/widgets/chart-parts.d.ts +20 -0
- package/dist/web/widgets/chart-parts.js +15 -0
- package/dist/web/widgets/charts.d.ts +5 -78
- package/dist/web/widgets/charts.js +7 -485
- package/dist/web/widgets/drawer-panel-class.d.ts +3 -0
- package/dist/web/widgets/drawer-panel-class.js +40 -0
- package/dist/web/widgets/drawer-resize-controls.d.ts +11 -0
- package/dist/web/widgets/drawer-resize-controls.js +12 -0
- package/dist/web/widgets/drawer-sizing.d.ts +19 -0
- package/dist/web/widgets/drawer-sizing.js +60 -0
- package/dist/web/widgets/drawer.js +17 -116
- package/dist/web/widgets/pdf-preview.js +9 -4
- package/dist/web/widgets/stacked-area-brush.d.ts +13 -0
- package/dist/web/widgets/stacked-area-brush.js +91 -0
- package/dist/web/widgets/stacked-area-chart.d.ts +6 -0
- package/dist/web/widgets/stacked-area-chart.js +45 -0
- package/dist/web/widgets/stacked-area-chrome.d.ts +7 -0
- package/dist/web/widgets/stacked-area-chrome.js +77 -0
- package/dist/web/widgets/stacked-area-geometry.d.ts +54 -0
- package/dist/web/widgets/stacked-area-geometry.js +83 -0
- package/dist/web/widgets/stacked-area-model.d.ts +11 -0
- package/dist/web/widgets/stacked-area-model.js +1 -0
- package/dist/web/widgets/stacked-area-plot.d.ts +3 -0
- package/dist/web/widgets/stacked-area-plot.js +51 -0
- package/dist/web/widgets/stacked-area-props.d.ts +83 -0
- package/dist/web/widgets/stacked-area-props.js +16 -0
- package/dist/web/widgets/stacked-area-svg.d.ts +3 -0
- package/dist/web/widgets/stacked-area-svg.js +38 -0
- package/dist/web/widgets/stacked-area-tooltip.d.ts +3 -0
- package/dist/web/widgets/stacked-area-tooltip.js +86 -0
- package/dist/web/widgets/use-drawer-resize.d.ts +22 -0
- package/dist/web/widgets/use-drawer-resize.js +52 -0
- package/dist/web/widgets/use-stacked-area-tooltip.d.ts +9 -0
- package/dist/web/widgets/use-stacked-area-tooltip.js +34 -0
- package/dist/web/widgets/use-stacked-area-window.d.ts +20 -0
- package/dist/web/widgets/use-stacked-area-window.js +76 -0
- package/package.json +9 -9
- package/src/changes.json +163 -0
|
@@ -1,279 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { cardColumnRoles } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
3
|
-
import { ArrowDown, ArrowUp, ChevronDown, ChevronRight, Search, SlidersHorizontal, X, } from "lucide-react";
|
|
4
|
-
import { Fragment, useId, useState, } from "react";
|
|
5
|
-
import { useOptionalLocale, useOptionalTranslation } from "../../index.js";
|
|
6
|
-
import { UNIFORM_BAR_CONTROLS_CLASSES } from "../layout/uniform-bar-controls.js";
|
|
1
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
2
|
import { cn } from "../lib/cn.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
3
|
+
import { renderTableContent } from "./data-table/table-content.js";
|
|
4
|
+
import { buildModelFacetCluster, hasToolbarContent, renderToolbar, renderToolbarDescription, } from "./data-table/toolbar.js";
|
|
5
|
+
import { useDataTableState } from "./data-table/use-data-table-state.js";
|
|
11
6
|
import { screenPaddingClassName } from "./layout.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
// `screenPadding`: on a list screen this wrapper IS the screen container,
|
|
8
|
+
// so it takes the same token as FormScreenShell/PageSection instead of its
|
|
9
|
+
// own inset — a list ends at the same footer distance as a form.
|
|
10
|
+
// Explicit `false` (vs. the default/omitted case) means a host that
|
|
11
|
+
// already pads its own children — a relatedList section nested in a
|
|
12
|
+
// tabs-mode Card — so this wrapper adds no inset of its
|
|
13
|
+
// own, or its content would sit ~24px deeper than sibling banners in the
|
|
14
|
+
// same Card body.
|
|
15
|
+
function screenPaddingOf(screenPadding) {
|
|
16
|
+
if (screenPadding === true)
|
|
17
|
+
return screenPaddingClassName;
|
|
18
|
+
return screenPadding === false ? undefined : "p-6";
|
|
23
19
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
const FILL_TABLE_CLASS = cn("[&>thead>tr>th]:sticky [&>thead>tr>th]:top-0 [&>thead>tr>th]:z-10 [&>thead>tr>th]:h-9 [&>thead>tr>th]:bg-muted [&>thead>tr>th]:px-2 [&>thead>tr>th]:text-[13px]", "[&>thead>tr>th]:shadow-[inset_0_-1px_0_var(--color-border)] [&>thead>tr>th_button]:text-[13px] [&>tbody>tr>td]:px-2 [&>tbody>tr>td]:py-0", "[&>*>tr>:first-child]:pl-6 [&>*>tr>:last-child]:pr-6");
|
|
31
|
-
// `!`: beats the first/last-child padding of FILL_TABLE_CLASS, which would
|
|
32
|
-
// otherwise widen the narrow toggle column / pad the full-width expansion cell.
|
|
33
|
-
// The nested table's sticky actions cell (last td) gets the muted surface
|
|
34
|
-
// instead of its own md:bg-background, so it blends into the area.
|
|
35
|
-
const EXPAND_TOGGLE_CELL_CLASS = "w-10 px-1! py-0!";
|
|
36
|
-
const EXPANSION_CELL_CLASS = "whitespace-normal bg-muted p-0! align-top md:[&_td:last-child]:bg-muted";
|
|
37
|
-
const EXPANSION_CONTENT_CLASS = "pt-1 pr-4 pb-3.5 pl-[52px]";
|
|
38
|
-
function RowExpandToggle({ expanded, controlsId, label, onToggle, testId, }) {
|
|
39
|
-
return (_jsx(UiButton, { type: "button", variant: "ghost", size: "icon", className: "size-8 shrink-0 max-md:size-11", "aria-expanded": expanded, ...(expanded && { "aria-controls": controlsId }), "aria-label": label, "data-testid": testId, onClick: (e) => {
|
|
40
|
-
e.stopPropagation();
|
|
41
|
-
onToggle();
|
|
42
|
-
}, onKeyDown: (e) => e.stopPropagation(), children: _jsx(ChevronRight, { className: cn("size-4 transition-transform", expanded && "rotate-90"), "aria-hidden": "true" }) }));
|
|
20
|
+
function wrapperClassName({ fillsHeight, screenPadding }) {
|
|
21
|
+
return cn("flex flex-col w-full",
|
|
22
|
+
// scrollBody: the wrapper fills its container so the footer sits at the
|
|
23
|
+
// same spot at 3 and 300 rows; the 24px insets live in toolbar, cells
|
|
24
|
+
// and footer, so no outer padding.
|
|
25
|
+
fillsHeight ? "h-full min-h-0" : cn("gap-4", screenPaddingOf(screenPadding)));
|
|
43
26
|
}
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
? (t?.("kumiko.list.count.noun", { count: count.toLocaleString(), noun }) ??
|
|
49
|
-
`${count.toLocaleString()} ${noun}`)
|
|
50
|
-
: count === 1
|
|
51
|
-
? (t?.("kumiko.list.count.one") ?? "1 entry")
|
|
52
|
-
: (t?.("kumiko.list.count.other", { count: count.toLocaleString() }) ??
|
|
53
|
-
`${count.toLocaleString()} entries`);
|
|
54
|
-
return (_jsx("div", { "data-testid": testId, className: TABLE_FOOTER_BAR_CLASS, children: _jsx("span", { "data-testid": `${testId}-count`, children: label }) }));
|
|
55
|
-
}
|
|
56
|
-
// Cell content is a ReactNode, so the tooltip text is read from the rendered DOM.
|
|
57
|
-
function showFullTextOnHover(event) {
|
|
58
|
-
const element = event.currentTarget;
|
|
59
|
-
element.title = element.scrollWidth > element.clientWidth ? (element.textContent ?? "") : "";
|
|
60
|
-
}
|
|
61
|
-
export function DefaultDataTable({ columns, rows, onRowClick, sort, onSortChange, emptyState, toolbarStart, toolbarDescription, toolbarEnd, pager, onReachEnd, loadingMore, hasMore, itemNoun, rowActions, rowActionMode, rowGrouping, rowTone, filterFacets, filterValues, onFilterChange, onFilterReset, dateRangeFacets, onDateRangeChange, testId, onCellChange, getRowTestId, getCellTestId, chromeless, scrollBody, screenPadding, expandedRowIds, onToggleRowExpanded, renderExpandedRow, }) {
|
|
62
|
-
// One locale/translate subscription per table — not per cell.
|
|
63
|
-
// Optional hooks: a bare DataTable outside LocaleProvider must not crash.
|
|
64
|
-
const tableTranslate = useOptionalTranslation();
|
|
65
|
-
const tableLocale = useOptionalLocale();
|
|
66
|
-
// Below 768px a table scrolls its columns out of reach with no visible
|
|
67
|
-
// affordance. Cards replace the table entirely below the breakpoint — same
|
|
68
|
-
// single-mount pattern as EmbeddedListInput/embedded-list-input.tsx.
|
|
69
|
-
const isNarrow = useIsNarrowViewport();
|
|
70
|
-
const [facetsOpenNarrow, setFacetsOpenNarrow] = useState(false);
|
|
71
|
-
const [toggledGroups, setToggledGroups] = useState(new Set());
|
|
72
|
-
// Toolbar wrapper: shared container for toolbar + table so the two belong
|
|
73
|
-
// together visually. The toolbar is NOT sticky: lists typically scroll with
|
|
74
|
-
// the page container, not internally. Sticky would compete with the topbar.
|
|
75
|
-
// The row click already runs rowClick actions, so with a clickable row they
|
|
76
|
-
// must not reappear in the kebab (a list whose only action is the rowClick
|
|
77
|
-
// one gets no actions column at all).
|
|
78
|
-
const rowIsLink = onRowClick !== undefined;
|
|
79
|
-
const menuActions = rowActions !== undefined && rowIsLink
|
|
80
|
-
? rowActions.filter((action) => action.rowClick !== true)
|
|
81
|
-
: rowActions;
|
|
82
|
-
// The table keeps every action in "inline" mode, and with editable cells the
|
|
83
|
-
// first cell is not a keyboard link (FirstCellLink is off), so the kebab is
|
|
84
|
-
// then the only keyboard route to the rowClick action.
|
|
85
|
-
const tableActions = rowActionMode === "inline" || onCellChange !== undefined ? rowActions : menuActions;
|
|
86
|
-
const rowClickAction = rowActions?.find((action) => action.rowClick === true);
|
|
87
|
-
const rowClickLabel = rowClickAction?.label;
|
|
88
|
-
const rowClickIsHidden = (row) => rowClickAction?.isVisible !== undefined && !rowClickAction.isVisible(row);
|
|
89
|
-
const hasTableActions = tableActions !== undefined && tableActions.length > 0;
|
|
90
|
-
const isEmpty = rows.length === 0;
|
|
91
|
-
const expansionIdPrefix = useId();
|
|
92
|
-
const isRowExpandable = renderExpandedRow !== undefined && onToggleRowExpanded !== undefined;
|
|
93
|
-
const rowTestId = (row) => getRowTestId?.(row) ?? `row-${row.id}`;
|
|
94
|
-
const expansionDomId = (row) => `${expansionIdPrefix}-${row.id}`;
|
|
95
|
-
// The expand toggle of a row, shared by table and card layout. Named after
|
|
96
|
-
// the first data column, the one a user recognizes the row by.
|
|
97
|
-
function renderExpandToggle(row) {
|
|
98
|
-
if (!isRowExpandable)
|
|
99
|
-
return null;
|
|
100
|
-
const expanded = expandedRowIds?.has(row.id) === true;
|
|
101
|
-
const firstColumn = columns[0];
|
|
102
|
-
const title = (firstColumn !== undefined ? cellTitle(row.values[firstColumn.field]) : undefined) ?? "";
|
|
103
|
-
const labelKey = expanded ? "kumiko.list.row.collapse" : "kumiko.list.row.expand";
|
|
104
|
-
return (_jsx(RowExpandToggle, { expanded: expanded, controlsId: expansionDomId(row), label: tableTranslate?.(labelKey, { title }) ?? `${expanded ? "Collapse" : "Expand"} ${title}`, onToggle: () => onToggleRowExpanded(row.id), testId: `${rowTestId(row)}-toggle` }));
|
|
105
|
-
}
|
|
106
|
-
function renderExpansion(row, as, colSpan) {
|
|
107
|
-
if (!isRowExpandable || expandedRowIds?.has(row.id) !== true)
|
|
108
|
-
return null;
|
|
109
|
-
const content = renderExpandedRow(row);
|
|
110
|
-
if (as === "item") {
|
|
111
|
-
return (_jsx("li", { id: expansionDomId(row), "data-testid": `${rowTestId(row)}-expansion`, className: "border-b border-border-row bg-muted px-4 pt-1 pb-3.5", children: content }));
|
|
112
|
-
}
|
|
113
|
-
return (_jsx(TableRow, { id: expansionDomId(row), "data-testid": `${rowTestId(row)}-expansion`, className: "hover:bg-transparent", children: _jsx(TableCell, { colSpan: colSpan, className: EXPANSION_CELL_CLASS, children: _jsx("div", { className: EXPANSION_CONTENT_CLASS, children: content }) }) }));
|
|
114
|
-
}
|
|
115
|
-
const fillsHeight = scrollBody === true;
|
|
116
|
-
const TableRoot = fillsHeight ? FlushTable : Table;
|
|
117
|
-
const emptyBlock = (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-empty` : "render-list-empty", className: cn("flex flex-col items-center justify-center p-12 text-sm text-muted-foreground gap-3", chromeless !== true && !fillsHeight && "rounded-md border border-dashed"), children: emptyState ?? _jsx("span", { children: tableTranslate?.("kumiko.list.no-entries") ?? "No entries." }) }));
|
|
118
|
-
const tableItems = buildTableItems(rows, rowGrouping, toggledGroups);
|
|
119
|
-
function renderGroupToggle(item) {
|
|
120
|
-
return (_jsxs("button", { type: "button", "aria-expanded": !item.collapsed, "data-testid": `row-group-${item.key}-toggle`, onClick: () => setToggledGroups((prev) => {
|
|
121
|
-
const next = new Set(prev);
|
|
122
|
-
if (!next.delete(item.key))
|
|
123
|
-
next.add(item.key);
|
|
124
|
-
return next;
|
|
125
|
-
}), className: "flex w-full items-center gap-2 px-4 py-2 text-left text-xs font-medium text-foreground-secondary", children: [item.collapsed ? (_jsx(ChevronRight, { className: "size-3.5" })) : (_jsx(ChevronDown, { className: "size-3.5" })), item.label] }));
|
|
126
|
-
}
|
|
127
|
-
function tableInner() {
|
|
128
|
-
if (isEmpty)
|
|
129
|
-
return emptyBlock;
|
|
130
|
-
return (_jsx("div", { className: cn(!fillsHeight && "overflow-hidden", !fillsHeight && chromeless !== true && "rounded-lg border bg-card"), children: _jsxs(TableRoot, { "data-testid": testId, className: fillsHeight ? FILL_TABLE_CLASS : undefined, children: [_jsx(TableHeader, { className: "bg-muted", children: _jsxs(TableRow, { className: "hover:bg-transparent", children: [isRowExpandable && (_jsx(TableHead, { "data-testid": "column-expand", className: EXPAND_TOGGLE_CELL_CLASS })), columns.map((col) => (_jsx(SortableHeader, { field: col.field, label: col.label, sortable: col.sortable === true, highlighted: col.highlighted === true, numeric: NUMERIC_COLUMN_TYPES.has(col.type), fitContent: col.fileDisplay !== undefined, ...(sort !== undefined && sort !== null && { sort }), ...(onSortChange !== undefined && { onSortChange }) }, col.field))), hasTableActions && (_jsx(TableHead, { "data-testid": "column-actions",
|
|
131
|
-
// From md: sticky right-0 + bg-muted (= header tone) so the
|
|
132
|
-
// action column stays at the right edge during horizontal
|
|
133
|
-
// scroll on wider screens. Below md, actions scroll in
|
|
134
|
-
// normal flow — sticky there would pin the column over the
|
|
135
|
-
// preceding data column instead of the (already narrow)
|
|
136
|
-
// page edge. No border-l: a permanent divider looks heavy;
|
|
137
|
-
// the sticky bg already sets the column apart during scroll.
|
|
138
|
-
className: "w-px text-right text-muted-foreground md:sticky md:right-0 md:z-10 md:bg-muted", "aria-label": tableTranslate?.("kumiko.list.actions") ?? "Actions" }))] }) }), _jsx(TableBody, { children: tableItems.map((item) => {
|
|
139
|
-
if (item.kind === "group") {
|
|
140
|
-
return (_jsx(TableRow, { "data-testid": `row-group-${item.key}`, children: _jsx(TableCell, { colSpan: columns.length + (isRowExpandable ? 1 : 0) + (hasTableActions ? 1 : 0), className: "bg-muted/40 p-0", children: renderGroupToggle(item) }) }, `group-${item.key}`));
|
|
141
|
-
}
|
|
142
|
-
const row = item.row;
|
|
143
|
-
const opensOnClick = rowIsLink && !rowClickIsHidden(row);
|
|
144
|
-
return (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-testid": rowTestId(row), "data-tone": rowTone?.(row), onClick: opensOnClick ? () => onRowClick(row) : undefined, className: cn(rowToneClass(rowTone?.(row)), opensOnClick && "cursor-pointer", fillsHeight && "h-10 border-border-row hover:bg-muted"), children: [isRowExpandable && (_jsx(TableCell, { "data-testid": getCellTestId?.(row, "expand") ?? `cell-${row.id}-expand`, className: EXPAND_TOGGLE_CELL_CLASS, children: renderExpandToggle(row) })), columns.map((col, colIndex) => (_jsx(TableCell, { "data-testid": getCellTestId?.(row, col.field) ?? `cell-${row.id}-${col.field}`, "data-highlighted": col.highlighted === true ? "true" : undefined,
|
|
145
|
-
// Text wraps instead of truncating: nowrap cells keep their
|
|
146
|
-
// full width, so a wide table scrolled sideways and its
|
|
147
|
-
// last column slid under the sticky actions column with
|
|
148
|
-
// nothing visibly cut off. max-w-xs bounds a single column;
|
|
149
|
-
// badges and dates stay nowrap by their own classes.
|
|
150
|
-
className: cn(col.fileDisplay === undefined
|
|
151
|
-
? "max-w-xs whitespace-normal break-words"
|
|
152
|
-
: FIT_CONTENT_CELL_CLASS, colIndex === 0
|
|
153
|
-
? "font-medium text-foreground"
|
|
154
|
-
: "text-foreground-secondary", NUMERIC_COLUMN_TYPES.has(col.type) && "text-right", TABULAR_COLUMN_TYPES.has(col.type) && "tabular-nums", col.highlighted === true && "bg-accent/40"), title: cellTitle(row.values[col.field]), children: _jsx(FirstCellLink, { enabled: colIndex === 0 && opensOnClick && onCellChange === undefined, onOpen: () => onRowClick?.(row), empty: isEmptyCellValue(row.values[col.field]), emptyLabel: rowClickLabel, children: _jsx(DataTableCell, { value: row.values[col.field], row: row.values, field: col.field, type: col.type, renderer: col.renderer, translate: tableTranslate, locale: tableLocale, ...(col.optionLabels !== undefined && {
|
|
155
|
-
optionLabels: col.optionLabels,
|
|
156
|
-
}), ...(col.optionTones !== undefined && { optionTones: col.optionTones }), ...(col.grouping !== undefined && { grouping: col.grouping }), ...(col.fileDisplay !== undefined && { fileDisplay: col.fileDisplay }), ...(onCellChange !== undefined && {
|
|
157
|
-
onChange: (value) => onCellChange(row.id, col.field, value),
|
|
158
|
-
}), ...cellLinkProp(col, row, !(colIndex === 0 && opensOnClick && onCellChange === undefined)) }) }) }, col.field))), hasTableActions && (_jsx(TableCell, { "data-testid": getCellTestId?.(row, "actions") ?? `cell-${row.id}-actions`,
|
|
159
|
-
// From md: sticky-right so the actions stay visible on the
|
|
160
|
-
// right edge during horizontal scroll. Below md, actions
|
|
161
|
-
// scroll with the row like any other cell — sticky there
|
|
162
|
-
// would hide the neighboring data column on narrow
|
|
163
|
-
// viewports. bg-background sets the column apart during
|
|
164
|
-
// scroll — no border-l (divider too heavy).
|
|
165
|
-
className: cn("whitespace-nowrap text-right md:sticky md:right-0 md:z-10 md:bg-background", fillsHeight && "md:bg-card"),
|
|
166
|
-
// Action-cell events must not trigger the row click/activation
|
|
167
|
-
// (typically "Open Detail" — the user wanted the action,
|
|
168
|
-
// not navigation). We stopPropagation for mouse and
|
|
169
|
-
// keyboard so a11y stays consistent.
|
|
170
|
-
onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: _jsx(RowActionsCell, { row: row, actions: tableActions, mode: rowActionMode, rowIsLink: rowIsLink }) }))] }), renderExpansion(row, "row", columns.length + (isRowExpandable ? 1 : 0) + (hasTableActions ? 1 : 0))] }, row.id));
|
|
171
|
-
}) })] }) }));
|
|
172
|
-
}
|
|
173
|
-
// No column headers below the breakpoint, so the click-to-sort affordance
|
|
174
|
-
// on SortableHeader has nothing to attach to. A single native <select> is
|
|
175
|
-
// the whole fix — no custom widget, no menu — fed straight from the
|
|
176
|
-
// columns marked sortable in the ViewModel. Options carry the resolved
|
|
177
|
-
// {field, dir} directly so onChange can look the pick up by value instead
|
|
178
|
-
// of parsing/casting the option string back apart.
|
|
179
|
-
const sortableColumns = columns.filter((col) => col.sortable);
|
|
180
|
-
const sortOptions = sortableColumns.flatMap((col) => [
|
|
181
|
-
{ value: `${col.field}:asc`, field: col.field, dir: "asc", label: `${col.label} ↑` },
|
|
182
|
-
{ value: `${col.field}:desc`, field: col.field, dir: "desc", label: `${col.label} ↓` },
|
|
183
|
-
]);
|
|
184
|
-
const { image: cardImageColumn, title: cardTitleColumn, status: cardStatusColumn, meta: cardMetaColumns, } = cardColumnRoles(columns);
|
|
185
|
-
function cardCell(row, col) {
|
|
186
|
-
// A bare check mark in the subtitle line says nothing without its column.
|
|
187
|
-
if (showsBareCheckMark(col.type, row.values[col.field], col.renderer))
|
|
188
|
-
return col.label;
|
|
189
|
-
return (_jsx(DataTableCell, { value: row.values[col.field], row: row.values, field: col.field, type: col.type, renderer: col.renderer, translate: tableTranslate, locale: tableLocale, ...(col.optionLabels !== undefined && { optionLabels: col.optionLabels }), ...(col.optionTones !== undefined && { optionTones: col.optionTones }), ...(col.grouping !== undefined && { grouping: col.grouping }), ...(col.fileDisplay !== undefined && { fileDisplay: col.fileDisplay, compactFile: true }), ...(onCellChange !== undefined && {
|
|
190
|
-
onChange: (value) => onCellChange(row.id, col.field, value),
|
|
191
|
-
}), ...cellLinkProp(col, row, !(rowIsLink && !rowClickIsHidden(row))) }));
|
|
192
|
-
}
|
|
193
|
-
function renderCard(row) {
|
|
194
|
-
const metaColumns = cardMetaColumns.filter((col) => !isEmptyCellValue(row.values[col.field]) &&
|
|
195
|
-
!isUnlabeledFalse(col.type, row.values[col.field], col.renderer));
|
|
196
|
-
const showStatus = cardStatusColumn !== undefined && !isEmptyCellValue(row.values[cardStatusColumn.field]);
|
|
197
|
-
const hasMenu = menuActions !== undefined && menuActions.length > 0;
|
|
198
|
-
const opensOnClick = rowIsLink && !rowClickIsHidden(row);
|
|
199
|
-
const body = (_jsxs(_Fragment, { children: [cardImageColumn?.fileDisplay !== undefined && (_jsx("div", { "data-testid": `card-image-${row.id}`, className: "shrink-0", children: _jsx(CardLeadingImage, { value: row.values[cardImageColumn.field], display: cardImageColumn.fileDisplay }) })), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-2", children: [cardTitleColumn !== undefined && (_jsx("span", { "data-testid": getCellTestId?.(row, cardTitleColumn.field) ??
|
|
200
|
-
`cell-${row.id}-${cardTitleColumn.field}`, className: "min-w-0 break-words text-base font-semibold text-foreground", children: cardCell(row, cardTitleColumn) })), showStatus && (_jsx("span", { "data-testid": getCellTestId?.(row, cardStatusColumn.field) ??
|
|
201
|
-
`cell-${row.id}-${cardStatusColumn.field}`, className: "shrink-0", children: cardCell(row, cardStatusColumn) }))] }), metaColumns.length > 0 && (_jsx("div", { className: "min-w-0 overflow-x-clip", children: _jsx("div", { "data-testid": `card-meta-${row.id}`, className: "-ms-3 flex min-w-0 flex-wrap items-center text-[13px] leading-5 tabular-nums text-foreground-secondary", children: metaColumns.map((col) => (_jsxs("div", { className: "flex h-5 min-w-0 max-w-full items-center", children: [_jsx("span", { "aria-hidden": "true", className: "w-3 shrink-0 text-center", children: "\u00B7" }), _jsx("div", { "data-testid": getCellTestId?.(row, col.field) ?? `cell-${row.id}-${col.field}`, className: "min-w-0 truncate leading-5", onPointerEnter: showFullTextOnHover, children: isBadgeColumn(col) ? (_jsx("span", { className: "inline-flex align-middle", children: cardCell(row, col) })) : (cardCell(row, col)) })] }, col.field))) }) }))] }), opensOnClick && (_jsx(ChevronRight, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }))] }));
|
|
202
|
-
const rowItem = (_jsxs("li", { "data-testid": rowTestId(row), "data-tone": rowTone?.(row), className: cn("flex min-h-[72px] items-center gap-2 border-b border-border-row py-3", rowToneClass(rowTone?.(row)), isRowExpandable ? "pl-2" : "pl-4", hasMenu ? "pr-2" : "pr-4"), ...(opensOnClick && { onClick: () => onRowClick(row) }), children: [renderExpandToggle(row), opensOnClick ? (_jsx("button", { type: "button", className: "flex min-w-0 flex-1 cursor-pointer items-center gap-2 bg-transparent p-0 text-left", onKeyDown: (e) => {
|
|
203
|
-
if (e.key !== "Enter")
|
|
204
|
-
return;
|
|
205
|
-
e.preventDefault();
|
|
206
|
-
e.stopPropagation();
|
|
207
|
-
onRowClick(row);
|
|
208
|
-
}, children: body })) : (body), hasMenu && (_jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: _jsx(RowActionsCell, { row: row, actions: menuActions, mode: rowActionMode, rowIsLink: rowIsLink, compact: true }) }))] }, row.id));
|
|
209
|
-
if (!isRowExpandable)
|
|
210
|
-
return rowItem;
|
|
211
|
-
// The expansion is a sibling <li>, outside the row's click target, so
|
|
212
|
-
// clicks in the sub-area never navigate the parent row.
|
|
213
|
-
return (_jsxs(Fragment, { children: [rowItem, renderExpansion(row, "item", 0)] }, row.id));
|
|
214
|
-
}
|
|
215
|
-
function cardsInner() {
|
|
216
|
-
if (isEmpty)
|
|
217
|
-
return emptyBlock;
|
|
218
|
-
const sortLabel = sort !== undefined && sort !== null
|
|
219
|
-
? (tableTranslate?.("kumiko.list.sort.by", {
|
|
220
|
-
column: columns.find((col) => col.field === sort.field)?.label ?? sort.field,
|
|
221
|
-
}) ?? `Sorted by ${sort.field}`)
|
|
222
|
-
: (tableTranslate?.("kumiko.list.sort.unsorted") ?? "Unsorted");
|
|
223
|
-
const SortArrow = sort?.dir === "desc" ? ArrowDown : ArrowUp;
|
|
224
|
-
// URL state can carry a sort on a column that is not sortable; without an
|
|
225
|
-
// option for it the select would claim "Unsorted" while the list is sorted.
|
|
226
|
-
const currentSortValue = sort !== undefined && sort !== null ? `${sort.field}:${sort.dir}` : "";
|
|
227
|
-
const currentSortIsUnlisted = sort !== undefined && sort !== null && !sortOptions.some((o) => o.value === currentSortValue);
|
|
228
|
-
return (_jsx("div", { "data-testid": testId, className: "contents", children: _jsxs("div", { "data-testid": testId !== undefined ? `${testId}-cards` : "render-list-cards", className: "flex flex-col", children: [onSortChange !== undefined && sortableColumns.length > 0 && (_jsxs("div", { className: "relative flex h-9 items-center gap-1.5 px-4 text-[13px] text-foreground-secondary", children: [_jsx("span", { "aria-hidden": "true", children: sortLabel }), sort !== undefined && sort !== null && (_jsx(SortArrow, { className: "size-3.5", "aria-hidden": "true" })), _jsxs("select", { "aria-label": tableTranslate?.("kumiko.list.sort.label") ?? "Sort", "data-testid": testId !== undefined ? `${testId}-sort` : "render-list-sort", value: currentSortValue, onChange: (e) => {
|
|
229
|
-
const raw = e.target.value;
|
|
230
|
-
if (raw === "") {
|
|
231
|
-
onSortChange(null);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const picked = sortOptions.find((o) => o.value === raw);
|
|
235
|
-
if (picked === undefined)
|
|
236
|
-
return;
|
|
237
|
-
onSortChange({ field: picked.field, dir: picked.dir });
|
|
238
|
-
}, className: "absolute inset-0 size-full cursor-pointer opacity-0", children: [_jsx("option", { value: "", children: tableTranslate?.("kumiko.list.sort.unsorted") ?? "Unsorted" }), currentSortIsUnlisted && (_jsx("option", { value: currentSortValue, children: `${columns.find((col) => col.field === sort.field)?.label ?? sort.field} ${sort.dir === "desc" ? "↓" : "↑"}` })), sortOptions.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value)))] })] })), _jsx("ul", { className: "m-0 list-none border-t border-border-row p-0", children: tableItems.map((item) => item.kind === "group" ? (_jsx("li", { "data-testid": `row-group-${item.key}`, className: "border-b border-border-row bg-muted/40", children: renderGroupToggle(item) }, `group-${item.key}`)) : (renderCard(item.row))) })] }) }));
|
|
239
|
-
}
|
|
240
|
-
const tableContent = isNarrow ? cardsInner() : tableInner();
|
|
241
|
-
// The pager is ALWAYS rendered below the table (even with rows=[]) so the
|
|
242
|
-
// user can page back on a filter hit of zero without losing the list.
|
|
243
|
-
// Except total === 0: then there is nothing to paginate. Incompatible with
|
|
244
|
-
// infinite scroll: the caller sets either pager OR onReachEnd.
|
|
245
|
-
const pagerElement = pager !== undefined && pager.total > 0 ? (_jsx(Pager, { page: pager.page, limit: pager.limit, total: pager.total, onPageChange: pager.onPageChange, ...(pager.pageSizeOptions !== undefined && { pageSizeOptions: pager.pageSizeOptions }), ...(pager.onPageSizeChange !== undefined && { onPageSizeChange: pager.onPageSizeChange }), ...(itemNoun !== undefined && { itemNoun }), testId: testId !== undefined ? `${testId}-pager` : "render-list-pager", pinned: fillsHeight })) : undefined;
|
|
246
|
-
const sentinelElement = pagerElement === undefined && onReachEnd !== undefined ? (_jsx(InfiniteSentinel, { onReachEnd: onReachEnd, loadingMore: loadingMore === true, hasMore: hasMore !== false, rowsCount: rows.length, testId: testId !== undefined ? `${testId}-sentinel` : "render-list-sentinel" })) : undefined;
|
|
247
|
-
// With scrollBody the footer (pager, else the entry count) sits OUTSIDE the
|
|
248
|
-
// scroll surface so it stays put at any row count; the infinite sentinel
|
|
249
|
-
// stays INSIDE, or it would never intersect the scrollport.
|
|
250
|
-
const content = fillsHeight ? (_jsxs(_Fragment, { children: [_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-scroll` : "render-list-scroll", className: "min-h-0 flex-1 overflow-auto border-t border-border bg-card", children: [tableContent, sentinelElement] }), pagerElement ?? (_jsx(DataTableFooter, { count: rows.length, ...(itemNoun !== undefined && { itemNoun }), testId: testId !== undefined ? `${testId}-footer` : "render-list-footer" }))] })) : (_jsxs(_Fragment, { children: [tableContent, pagerElement, sentinelElement] }));
|
|
251
|
-
const hasFacets = filterFacets !== undefined && filterFacets.length > 0 && onFilterChange !== undefined;
|
|
252
|
-
const hasDateRangeFacets = dateRangeFacets !== undefined && dateRangeFacets.length > 0 && onDateRangeChange !== undefined;
|
|
253
|
-
const hasActiveFilters = filterValues !== undefined && Object.values(filterValues).some((v) => v.length > 0);
|
|
254
|
-
const facetCluster = hasFacets || hasDateRangeFacets ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [hasFacets &&
|
|
255
|
-
filterFacets.map((facet) => (_jsx(FacetFilter, { facet: facet, selected: filterValues?.[facet.field] ?? [], onChange: onFilterChange, dense: fillsHeight }, facet.field))), hasDateRangeFacets &&
|
|
256
|
-
dateRangeFacets.map((facet) => (_jsx(DateRangeFacetFilter, { facet: facet, onChange: onDateRangeChange }, facet.field))), hasActiveFilters && onFilterReset !== undefined && (_jsxs(UiButton, { variant: "ghost", size: "sm", className: "h-9 px-2", onClick: onFilterReset, "data-testid": "facet-reset", children: [tableTranslate?.("kumiko.list.filter.reset") ?? "Reset", _jsx(X, {})] }))] })) : undefined;
|
|
257
|
-
const collapsesFacetsBehindButton = isNarrow && fillsHeight && facetCluster !== undefined;
|
|
258
|
-
const hasToolbar = toolbarStart !== undefined || toolbarEnd !== undefined || facetCluster !== undefined;
|
|
27
|
+
export function DefaultDataTable(props) {
|
|
28
|
+
const model = useDataTableState(props);
|
|
29
|
+
const facetCluster = buildModelFacetCluster(model);
|
|
30
|
+
const hasToolbar = hasToolbarContent(model, facetCluster);
|
|
259
31
|
// dashboard-01 pattern: the toolbar (search + facets + "+ New") sits ABOVE
|
|
260
32
|
// the table in the same padding block; no separate bg bar, no screen title
|
|
261
|
-
// (that lives in the shell's breadcrumb).
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// and footer, so no outer padding.
|
|
266
|
-
fillsHeight
|
|
267
|
-
? "h-full min-h-0"
|
|
268
|
-
: cn("gap-4", screenPadding === true
|
|
269
|
-
? screenPaddingClassName
|
|
270
|
-
: screenPadding === false
|
|
271
|
-
? undefined
|
|
272
|
-
: "p-6")), children: [hasToolbar && (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-toolbar` : "render-list-toolbar", className: cn("flex flex-wrap items-center", UNIFORM_BAR_CONTROLS_CLASSES, fillsHeight
|
|
273
|
-
? isNarrow
|
|
274
|
-
? "shrink-0 gap-2 p-3"
|
|
275
|
-
: "min-h-13 shrink-0 gap-2 px-6 py-2 md:py-0"
|
|
276
|
-
: "gap-3"), children: [toolbarStart !== undefined && (_jsxs("div", { className: cn(fillsHeight
|
|
277
|
-
? "relative w-full md:w-[300px] max-md:flex-1 [&_input]:border-border-strong [&_input]:pl-8 [&_input]:max-md:h-11"
|
|
278
|
-
: "flex-1 min-w-48 max-w-sm"), children: [fillsHeight && (_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 z-10 size-4 -translate-y-1/2 text-muted-foreground", "aria-hidden": "true" })), toolbarStart] })), collapsesFacetsBehindButton ? (_jsxs(_Fragment, { children: [_jsx(UiButton, { type: "button", variant: "outline", size: "icon", className: "size-11", "aria-label": tableTranslate?.("kumiko.list.filter.toggle") ?? "Filter", "aria-expanded": facetsOpenNarrow, "data-testid": testId !== undefined ? `${testId}-filter-toggle` : "filter-toggle", onClick: () => setFacetsOpenNarrow((open) => !open), children: _jsx(SlidersHorizontal, { className: "size-4", "aria-hidden": "true" }) }), facetsOpenNarrow && _jsx("div", { className: "w-full", children: facetCluster })] })) : (facetCluster), toolbarEnd !== undefined && (_jsx("div", { className: "flex flex-wrap items-center gap-2 ml-auto", children: toolbarEnd }))] })), toolbarDescription !== undefined && (_jsx("p", { "data-testid": testId !== undefined ? `${testId}-description` : "render-list-description", className: cn("m-0 pb-2 text-sm text-muted-foreground", fillsHeight && (isNarrow ? "px-3" : "px-6"), fillsHeight && !hasToolbar && "pt-4"), children: toolbarDescription })), content] }));
|
|
33
|
+
// (that lives in the shell's breadcrumb). The toolbar is NOT sticky: lists
|
|
34
|
+
// typically scroll with the page container, not internally. Sticky would
|
|
35
|
+
// compete with the topbar.
|
|
36
|
+
return (_jsxs("div", { className: wrapperClassName(model), children: [hasToolbar && renderToolbar(model, facetCluster), renderToolbarDescription(model, hasToolbar), renderTableContent(model)] }));
|
|
279
37
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { type FileValueListProps } from "../../index.ts";
|
|
3
|
-
export declare function FileValueList({ fileIds, testId }: FileValueListProps): ReactNode;
|
|
3
|
+
export declare function FileValueList({ fileIds, previewOpenByDefault, testId, }: FileValueListProps): ReactNode;
|
|
@@ -6,17 +6,17 @@ import { Button } from "../ui/button.js";
|
|
|
6
6
|
import { PdfPreview } from "../widgets/pdf-preview.js";
|
|
7
7
|
import { fileOriginalUrl, useFileMeta } from "./file-thumbnail.js";
|
|
8
8
|
const PDF_MIME_TYPE = "application/pdf";
|
|
9
|
-
function FileValueRow({ fileId, testId, }) {
|
|
9
|
+
function FileValueRow({ fileId, previewOpenInitially, testId, }) {
|
|
10
10
|
const t = useTranslation();
|
|
11
11
|
const meta = useFileMeta(fileId);
|
|
12
|
-
const [previewOpen, setPreviewOpen] = useState(
|
|
12
|
+
const [previewOpen, setPreviewOpen] = useState(previewOpenInitially);
|
|
13
13
|
if (!isUuid(fileId))
|
|
14
14
|
return _jsx("span", { className: "text-sm", children: fileId });
|
|
15
15
|
const downloadUrl = fileOriginalUrl(fileId);
|
|
16
|
-
return (_jsxs("li", { className: "flex flex-col gap-2", "data-testid": testId, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx("span", { className: "text-sm", children: meta?.fileName ?? fileId.slice(0, 8) }), _jsx("a", { href: downloadUrl, download: meta?.fileName ?? "", className: "text-primary text-sm underline underline-offset-2", ...(testId !== undefined && { "data-testid": `${testId}-download` }), children: t("kumiko.field.file-list.download") }), meta?.mimeType === PDF_MIME_TYPE && (_jsx(Button, { type: "button", variant: "link", size: "sm", "aria-expanded": previewOpen, onClick: () => setPreviewOpen((open) => !open), ...(testId !== undefined && { "data-testid": `${testId}-preview-toggle` }), children: t(previewOpen ? "kumiko.field.file-list.hidePreview" : "kumiko.field.file-list.preview") }))] }), previewOpen && (_jsx(PdfPreview, { source: { url: downloadUrl }, testId: `file-preview-${fileId}` }))] }));
|
|
16
|
+
return (_jsxs("li", { className: "flex flex-col gap-2", "data-testid": testId, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx("span", { className: "text-sm", children: meta?.fileName ?? fileId.slice(0, 8) }), _jsx("a", { href: downloadUrl, download: meta?.fileName ?? "", className: "text-primary text-sm underline underline-offset-2", ...(testId !== undefined && { "data-testid": `${testId}-download` }), children: t("kumiko.field.file-list.download") }), meta?.mimeType === PDF_MIME_TYPE && (_jsx(Button, { type: "button", variant: "link", size: "sm", "aria-expanded": previewOpen, onClick: () => setPreviewOpen((open) => !open), ...(testId !== undefined && { "data-testid": `${testId}-preview-toggle` }), children: t(previewOpen ? "kumiko.field.file-list.hidePreview" : "kumiko.field.file-list.preview") }))] }), previewOpen && meta?.mimeType === PDF_MIME_TYPE && (_jsx(PdfPreview, { source: { url: downloadUrl }, testId: `file-preview-${fileId}` }))] }));
|
|
17
17
|
}
|
|
18
|
-
export function FileValueList({ fileIds, testId }) {
|
|
18
|
+
export function FileValueList({ fileIds, previewOpenByDefault = false, testId, }) {
|
|
19
19
|
if (fileIds.length === 0)
|
|
20
20
|
return null;
|
|
21
|
-
return (_jsx("ul", { className: "m-0 flex list-none flex-col gap-3 p-0", "data-testid": testId, children: fileIds.map((fileId) => (_jsx(FileValueRow, { fileId: fileId, ...(testId !== undefined && { testId: `${testId}-${fileId}` }) }, fileId))) }));
|
|
21
|
+
return (_jsx("ul", { className: "m-0 flex list-none flex-col gap-3 p-0", "data-testid": testId, children: fileIds.map((fileId, index) => (_jsx(FileValueRow, { fileId: fileId, previewOpenInitially: previewOpenByDefault && index === fileIds.length - 1, ...(testId !== undefined && { testId: `${testId}-${fileId}` }) }, fileId))) }));
|
|
22
22
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import type { useTranslation } from "../../index.ts";
|
|
3
|
+
type Translate = ReturnType<typeof useTranslation>;
|
|
4
|
+
export type FormFooterModel = {
|
|
5
|
+
readonly t: Translate;
|
|
6
|
+
readonly testId: string | undefined;
|
|
7
|
+
readonly chromeless: boolean;
|
|
8
|
+
readonly stickyActions: boolean | undefined;
|
|
9
|
+
readonly fillHeight: boolean | undefined;
|
|
10
|
+
readonly unsavedCount: number | undefined;
|
|
11
|
+
readonly railed: boolean;
|
|
12
|
+
readonly pinned: boolean;
|
|
13
|
+
readonly secondaryActions: ReactNode;
|
|
14
|
+
readonly renderedSecondary: ReactNode;
|
|
15
|
+
readonly renderedActions: ReactNode;
|
|
16
|
+
readonly hasNonPrimaryOverflow: boolean;
|
|
17
|
+
readonly fixedFooterStyle: CSSProperties | undefined;
|
|
18
|
+
};
|
|
19
|
+
export declare function unsavedStatusText(t: Translate, unsavedCount: number): string;
|
|
20
|
+
export declare function renderNarrowPinnedFooter(model: Pick<FormFooterModel, "t" | "testId" | "unsavedCount">, nodes: readonly ReactNode[], keyboardInset: number): ReactNode;
|
|
21
|
+
export declare function footerRowClassName(model: FormFooterModel): string;
|
|
22
|
+
export declare function renderUnsavedStatus(model: FormFooterModel, unsavedCount: number): ReactNode;
|
|
23
|
+
export declare function renderSecondaryActionsGroup(model: FormFooterModel): ReactNode;
|
|
24
|
+
export declare function renderPrimaryActionsGroup(model: FormFooterModel): ReactNode;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { cardFooterBorder } from "./card-styles.js";
|
|
4
|
+
import { NarrowPinnedFooter } from "./narrow-pinned-footer.js";
|
|
5
|
+
import { STICKY_FOOTER_SAFE_AREA_CLASS } from "./sticky-action-bar.js";
|
|
6
|
+
export function unsavedStatusText(t, unsavedCount) {
|
|
7
|
+
if (unsavedCount === 0)
|
|
8
|
+
return t("kumiko.form.noUnsavedChanges");
|
|
9
|
+
if (unsavedCount === 1)
|
|
10
|
+
return t("kumiko.form.unsaved.one");
|
|
11
|
+
return t("kumiko.form.unsaved.other", { count: unsavedCount });
|
|
12
|
+
}
|
|
13
|
+
export function renderNarrowPinnedFooter(model, nodes, keyboardInset) {
|
|
14
|
+
const { t, testId, unsavedCount } = model;
|
|
15
|
+
return (_jsx(NarrowPinnedFooter, { nodes: nodes, overflowLabel: unsavedCount !== undefined && unsavedCount > 0
|
|
16
|
+
? `${t("kumiko.list.row-actions.more")}, ${unsavedStatusText(t, unsavedCount)}`
|
|
17
|
+
: t("kumiko.list.row-actions.more"), unsavedCount: unsavedCount, unsavedStatus: unsavedCount !== undefined && unsavedCount > 0
|
|
18
|
+
? unsavedStatusText(t, unsavedCount)
|
|
19
|
+
: undefined, testId: testId, keyboardInset: keyboardInset }));
|
|
20
|
+
}
|
|
21
|
+
export function footerRowClassName(model) {
|
|
22
|
+
const { pinned, chromeless, fillHeight, railed } = model;
|
|
23
|
+
return cn(pinned
|
|
24
|
+
? cn("flex h-14 shrink-0 items-center justify-end gap-2 border-t border-border bg-card px-6", !railed && "md:pl-10")
|
|
25
|
+
: "flex flex-col-reverse gap-3 py-3 sm:flex-row sm:items-center sm:justify-between sm:py-4", !pinned && !chromeless && "px-[var(--card-padding)]", !pinned && !chromeless && cardFooterBorder, !pinned && fillHeight === true && "shrink-0");
|
|
26
|
+
}
|
|
27
|
+
export function renderUnsavedStatus(model, unsavedCount) {
|
|
28
|
+
const { t, testId, pinned } = model;
|
|
29
|
+
return (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-unsaved` : undefined, className: cn("mr-auto flex min-w-0 items-center gap-2 text-[13px]",
|
|
30
|
+
// Only the stacked (flex-col-reverse) footer moves the status above the
|
|
31
|
+
// actions; the pinned single-row footer keeps it on the left.
|
|
32
|
+
!pinned && "max-sm:order-last", unsavedCount > 0 ? "text-foreground-secondary" : "text-muted-foreground"), children: [unsavedCount > 0 && (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-status-active", "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: unsavedStatusText(t, unsavedCount) })] }));
|
|
33
|
+
}
|
|
34
|
+
export function renderSecondaryActionsGroup(model) {
|
|
35
|
+
const { testId, pinned, hasNonPrimaryOverflow, renderedSecondary } = model;
|
|
36
|
+
return (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions-secondary` : undefined, className: cn(
|
|
37
|
+
// min-h-11: link-variant buttons (Copy link) are h-auto and would fall
|
|
38
|
+
// below the 24px touch-target minimum on narrow viewports.
|
|
39
|
+
"flex flex-wrap items-center gap-2 max-sm:[&_button]:min-h-11 max-sm:[&_button]:px-2 max-sm:[&_button]:text-xs", pinned && hasNonPrimaryOverflow && "mr-auto"), children: renderedSecondary }));
|
|
40
|
+
}
|
|
41
|
+
export function renderPrimaryActionsGroup(model) {
|
|
42
|
+
const { testId, pinned, stickyActions, fixedFooterStyle, renderedActions } = model;
|
|
43
|
+
return (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, style: fixedFooterStyle, className: cn("flex items-center gap-2", pinned
|
|
44
|
+
? "shrink-0 max-md:[&>button]:min-h-11"
|
|
45
|
+
: "flex-wrap max-sm:w-full max-sm:[&>button]:flex-1 max-sm:[&>button]:min-h-11 sm:ml-auto",
|
|
46
|
+
// Below sm (640px): pin only the primary action to the viewport
|
|
47
|
+
// bottom instead of normal flow, so a virtual keyboard shrinking
|
|
48
|
+
// the viewport can't push it out of reach. `fixed`
|
|
49
|
+
// escapes the card's `overflow-hidden` (only transform/filter/
|
|
50
|
+
// contain ancestors trap it, confirmed against
|
|
51
|
+
// AppLayout/SidebarInset — neither sets those).
|
|
52
|
+
stickyActions === true &&
|
|
53
|
+
!pinned &&
|
|
54
|
+
cn("max-sm:fixed max-sm:inset-x-0 max-sm:bottom-0 max-sm:z-20 max-sm:bg-background max-sm:p-4 max-sm:shadow-[0_-4px_12px_-4px_rgb(0_0_0_/_0.15)]", STICKY_FOOTER_SAFE_AREA_CLASS)), children: renderedActions }));
|
|
55
|
+
}
|
|
@@ -2,13 +2,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Children, Fragment, isValidElement, useContext, useEffect, useRef, useState, } from "react";
|
|
3
3
|
import { STICKY_PRIMARY_ACTION_PROP, useTranslation, } from "../../index.js";
|
|
4
4
|
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { footerRowClassName, renderNarrowPinnedFooter, renderPrimaryActionsGroup, renderSecondaryActionsGroup, renderUnsavedStatus, } from "./form-footer-parts.js";
|
|
5
6
|
import { screenPaddingClassName, screenWidthClassName } from "./layout.js";
|
|
6
|
-
import {
|
|
7
|
-
import { STICKY_FOOTER_SAFE_AREA_CLASS, STICKY_FOOTER_SPACER_CLASS } from "./sticky-action-bar.js";
|
|
7
|
+
import { STICKY_FOOTER_SPACER_CLASS } from "./sticky-action-bar.js";
|
|
8
8
|
import { useKeyboardInset } from "./use-keyboard-inset.js";
|
|
9
9
|
import { useIsBelowSmViewport } from "./use-narrow-viewport.js";
|
|
10
10
|
import "../react-css-custom-properties.js";
|
|
11
|
-
import {
|
|
11
|
+
import { cardHeaderBorder } from "./card-styles.js";
|
|
12
12
|
import { InsideFormContext, ScreenFormContext, ScreenWidthContext } from "./contexts.js";
|
|
13
13
|
// Extension sections that render their own <form> via BareFormProvider
|
|
14
14
|
// (legacy custom-form pattern, e.g. ChangeEmailSection) land
|
|
@@ -138,21 +138,7 @@ function flattenActionNodes(node) {
|
|
|
138
138
|
}
|
|
139
139
|
return Children.toArray(node);
|
|
140
140
|
}
|
|
141
|
-
function
|
|
142
|
-
if (unsavedCount === 0)
|
|
143
|
-
return t("kumiko.form.noUnsavedChanges");
|
|
144
|
-
if (unsavedCount === 1)
|
|
145
|
-
return t("kumiko.form.unsaved.one");
|
|
146
|
-
return t("kumiko.form.unsaved.other", { count: unsavedCount });
|
|
147
|
-
}
|
|
148
|
-
// Footer wrapper for DefaultForm's card and chromeless layouts alike — only
|
|
149
|
-
// the card-derived horizontal padding/border differs between them.
|
|
150
|
-
export function FormFooter({ actions, secondaryActions, testId, chromeless, stickyActions, fillHeight, unsavedCount, railed = false, }) {
|
|
151
|
-
const t = useTranslation();
|
|
152
|
-
const isBelowSm = useIsBelowSmViewport();
|
|
153
|
-
const keyboardInset = useKeyboardInset();
|
|
154
|
-
if (actions === undefined && secondaryActions === undefined)
|
|
155
|
-
return null;
|
|
141
|
+
function splitStickyActions(stickyActions, actions, secondaryActions) {
|
|
156
142
|
// Only split when sticky: the non-sticky (regular, non-wizard) footer must
|
|
157
143
|
// reproduce the previous DOM exactly (form-action-bar.test.tsx pins
|
|
158
144
|
// `-actions`/`-actions-secondary` content 1:1 to the `actions`/
|
|
@@ -170,6 +156,17 @@ export function FormFooter({ actions, secondaryActions, testId, chromeless, stic
|
|
|
170
156
|
const renderedActions = stickyActions === true ? (hasPrimaryAction ? primaryActionNodes : actions) : actions;
|
|
171
157
|
const hasNonPrimaryOverflow = nonPrimaryActionNodes !== undefined && nonPrimaryActionNodes.length > 0;
|
|
172
158
|
const renderedSecondary = stickyActions === true && hasPrimaryAction ? (_jsxs(_Fragment, { children: [secondaryActions, nonPrimaryActionNodes] })) : (secondaryActions);
|
|
159
|
+
return { renderedActions, renderedSecondary, hasNonPrimaryOverflow };
|
|
160
|
+
}
|
|
161
|
+
// Footer wrapper for DefaultForm's card and chromeless layouts alike — only
|
|
162
|
+
// the card-derived horizontal padding/border differs between them.
|
|
163
|
+
export function FormFooter({ actions, secondaryActions, testId, chromeless, stickyActions, fillHeight, unsavedCount, railed = false, }) {
|
|
164
|
+
const t = useTranslation();
|
|
165
|
+
const isBelowSm = useIsBelowSmViewport();
|
|
166
|
+
const keyboardInset = useKeyboardInset();
|
|
167
|
+
if (actions === undefined && secondaryActions === undefined)
|
|
168
|
+
return null;
|
|
169
|
+
const { renderedActions, renderedSecondary, hasNonPrimaryOverflow } = splitStickyActions(stickyActions, actions, secondaryActions);
|
|
173
170
|
const pinned = isPinnedFooter(stickyActions, fillHeight);
|
|
174
171
|
const fixedFooterStyle = stickyActions === true && !pinned && isBelowSm && keyboardInset > 0
|
|
175
172
|
? { bottom: keyboardInset }
|
|
@@ -177,32 +174,25 @@ export function FormFooter({ actions, secondaryActions, testId, chromeless, stic
|
|
|
177
174
|
// Below sm the pinned footer swaps to NarrowPinnedFooter; the subtree remounts, so a
|
|
178
175
|
// confirm dialog owned by an action button closes on a breakpoint change (rotate, resize).
|
|
179
176
|
if (pinned && isBelowSm) {
|
|
180
|
-
return (
|
|
181
|
-
? `${t("kumiko.list.row-actions.more")}, ${unsavedStatusText(t, unsavedCount)}`
|
|
182
|
-
: t("kumiko.list.row-actions.more"), unsavedCount: unsavedCount, unsavedStatus: unsavedCount !== undefined && unsavedCount > 0
|
|
183
|
-
? unsavedStatusText(t, unsavedCount)
|
|
184
|
-
: undefined, testId: testId, keyboardInset: keyboardInset }));
|
|
177
|
+
return renderNarrowPinnedFooter({ t, testId, unsavedCount }, [...flattenActionNodes(secondaryActions), ...flattenActionNodes(actions)], keyboardInset);
|
|
185
178
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
stickyActions === true &&
|
|
204
|
-
!pinned &&
|
|
205
|
-
cn("max-sm:fixed max-sm:inset-x-0 max-sm:bottom-0 max-sm:z-20 max-sm:bg-background max-sm:p-4 max-sm:shadow-[0_-4px_12px_-4px_rgb(0_0_0_/_0.15)]", STICKY_FOOTER_SAFE_AREA_CLASS)), children: renderedActions }))] }));
|
|
179
|
+
const model = {
|
|
180
|
+
t,
|
|
181
|
+
testId,
|
|
182
|
+
chromeless,
|
|
183
|
+
stickyActions,
|
|
184
|
+
fillHeight,
|
|
185
|
+
unsavedCount,
|
|
186
|
+
railed,
|
|
187
|
+
pinned,
|
|
188
|
+
secondaryActions,
|
|
189
|
+
renderedSecondary,
|
|
190
|
+
renderedActions,
|
|
191
|
+
hasNonPrimaryOverflow,
|
|
192
|
+
fixedFooterStyle,
|
|
193
|
+
};
|
|
194
|
+
return (_jsxs("div", { className: footerRowClassName(model), children: [unsavedCount !== undefined && renderUnsavedStatus(model, unsavedCount), (secondaryActions !== undefined || hasNonPrimaryOverflow) &&
|
|
195
|
+
renderSecondaryActionsGroup(model), renderedActions !== undefined && renderPrimaryActionsGroup(model)] }));
|
|
206
196
|
}
|
|
207
197
|
export const SECTION_NAV_MIN_ITEMS = 3;
|
|
208
198
|
// "On this page" navigation next to a screen form. The active entry follows
|