@cosmicdrift/kumiko-renderer 0.388.0 → 0.389.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.
Files changed (161) hide show
  1. package/dist/app/base-path-context.d.ts +9 -0
  2. package/dist/app/base-path-context.js +13 -0
  3. package/dist/app/base-path.d.ts +3 -0
  4. package/dist/app/base-path.js +27 -0
  5. package/dist/app/config-edit-body.d.ts +1 -1
  6. package/dist/app/config-edit-body.js +15 -6
  7. package/dist/app/config-edit-without-secret-fields.d.ts +2 -0
  8. package/dist/app/config-edit-without-secret-fields.js +28 -0
  9. package/dist/app/entity-list-facet-specs.d.ts +3 -0
  10. package/dist/app/entity-list-facet-specs.js +65 -0
  11. package/dist/app/entity-list-screen.js +74 -206
  12. package/dist/app/kumiko-screen.js +6 -14
  13. package/dist/app/list-bulk-actions.d.ts +18 -0
  14. package/dist/app/list-bulk-actions.js +54 -0
  15. package/dist/app/locked-screen-header.d.ts +9 -0
  16. package/dist/app/locked-screen-header.js +33 -0
  17. package/dist/app/projection-list-body.js +51 -46
  18. package/dist/app/row-actions.d.ts +3 -0
  19. package/dist/app/row-actions.js +10 -5
  20. package/dist/app/screen-shared.js +9 -2
  21. package/dist/app/use-entity-range-facets.d.ts +24 -0
  22. package/dist/app/use-entity-range-facets.js +64 -0
  23. package/dist/app/use-projection-date-range.d.ts +12 -0
  24. package/dist/app/use-projection-date-range.js +21 -0
  25. package/dist/app/use-screen-list-rows.d.ts +37 -0
  26. package/dist/app/use-screen-list-rows.js +104 -0
  27. package/dist/app/use-screen-list-table.d.ts +24 -0
  28. package/dist/app/use-screen-list-table.js +46 -0
  29. package/dist/components/list-group-page-bridges.d.ts +3 -0
  30. package/dist/components/list-group-page-bridges.js +31 -0
  31. package/dist/components/list-state-store.d.ts +16 -0
  32. package/dist/components/list-state-store.js +31 -0
  33. package/dist/components/related-list-section.js +37 -89
  34. package/dist/components/render-field.js +6 -5
  35. package/dist/components/render-list-toolbar-action.d.ts +27 -0
  36. package/dist/components/render-list-toolbar-action.js +40 -0
  37. package/dist/components/render-list.d.ts +13 -29
  38. package/dist/components/render-list.js +42 -50
  39. package/dist/current-user.d.ts +2 -0
  40. package/dist/data-table-aggregate-types.d.ts +47 -0
  41. package/dist/data-table-aggregate-types.js +1 -0
  42. package/dist/data-table-bulk-types.d.ts +27 -0
  43. package/dist/data-table-bulk-types.js +1 -0
  44. package/dist/data-table-facet-types.d.ts +36 -0
  45. package/dist/data-table-facet-types.js +1 -0
  46. package/dist/data-table-view-types.d.ts +73 -0
  47. package/dist/data-table-view-types.js +1 -0
  48. package/dist/headless/view-model/edit.js +3 -1
  49. package/dist/headless/view-model/types.d.ts +4 -2
  50. package/dist/hooks/list-column-layout.d.ts +19 -0
  51. package/dist/hooks/list-column-layout.js +36 -0
  52. package/dist/hooks/list-group-pages.d.ts +26 -0
  53. package/dist/hooks/list-group-pages.js +37 -0
  54. package/dist/hooks/list-reset.d.ts +8 -0
  55. package/dist/hooks/list-reset.js +15 -0
  56. package/dist/hooks/list-server-groups.d.ts +39 -0
  57. package/dist/hooks/list-server-groups.js +84 -0
  58. package/dist/hooks/list-state-schema.d.ts +28 -0
  59. package/dist/hooks/list-state-schema.js +76 -0
  60. package/dist/hooks/use-bulk-run.d.ts +41 -0
  61. package/dist/hooks/use-bulk-run.js +65 -0
  62. package/dist/hooks/use-group-load-state.d.ts +11 -0
  63. package/dist/hooks/use-group-load-state.js +31 -0
  64. package/dist/hooks/use-list-aggregates.d.ts +38 -0
  65. package/dist/hooks/use-list-aggregates.js +107 -0
  66. package/dist/hooks/use-list-table.d.ts +38 -0
  67. package/dist/hooks/use-list-table.js +91 -0
  68. package/dist/hooks/use-list-view-state.d.ts +43 -0
  69. package/dist/hooks/use-list-view-state.js +161 -0
  70. package/dist/hooks/use-related-list-rows.d.ts +27 -0
  71. package/dist/hooks/use-related-list-rows.js +81 -0
  72. package/dist/hooks/use-related-list-state.d.ts +15 -0
  73. package/dist/hooks/use-related-list-state.js +31 -0
  74. package/dist/hooks/use-remembered-list-params.d.ts +13 -0
  75. package/dist/hooks/use-remembered-list-params.js +53 -0
  76. package/dist/hooks/use-remembered-local-list-state.d.ts +26 -0
  77. package/dist/hooks/use-remembered-local-list-state.js +63 -0
  78. package/dist/i18n-defaults.js +53 -0
  79. package/dist/index.d.ts +3 -1
  80. package/dist/index.js +2 -0
  81. package/dist/primitives.d.ts +11 -47
  82. package/dist/web/app/create-app.d.ts +5 -0
  83. package/dist/web/app/create-app.js +7 -4
  84. package/dist/web/app/history-leave-guard.d.ts +6 -0
  85. package/dist/web/app/history-leave-guard.js +115 -0
  86. package/dist/web/app/list-state-store-boot.d.ts +6 -0
  87. package/dist/web/app/list-state-store-boot.js +15 -0
  88. package/dist/web/app/nav.js +7 -54
  89. package/dist/web/app/session-list-state-store.d.ts +15 -0
  90. package/dist/web/app/session-list-state-store.js +117 -0
  91. package/dist/web/index.d.ts +1 -0
  92. package/dist/web/index.js +1 -0
  93. package/dist/web/lib/export-xlsx-request.d.ts +11 -0
  94. package/dist/web/lib/export-xlsx-request.js +46 -0
  95. package/dist/web/primitives/data-table/actions-column.d.ts +16 -0
  96. package/dist/web/primitives/data-table/actions-column.js +34 -0
  97. package/dist/web/primitives/data-table/bulk-selection-bar.d.ts +20 -0
  98. package/dist/web/primitives/data-table/bulk-selection-bar.js +37 -0
  99. package/dist/web/primitives/data-table/card-sort-select.d.ts +14 -0
  100. package/dist/web/primitives/data-table/card-sort-select.js +40 -0
  101. package/dist/web/primitives/data-table/column-layout-mode.d.ts +5 -0
  102. package/dist/web/primitives/data-table/column-layout-mode.js +10 -0
  103. package/dist/web/primitives/data-table/column-picker-section.d.ts +7 -0
  104. package/dist/web/primitives/data-table/column-picker-section.js +35 -0
  105. package/dist/web/primitives/data-table/column-resize-handle.d.ts +14 -0
  106. package/dist/web/primitives/data-table/column-resize-handle.js +46 -0
  107. package/dist/web/primitives/data-table/density.d.ts +14 -0
  108. package/dist/web/primitives/data-table/density.js +20 -0
  109. package/dist/web/primitives/data-table/export-button.d.ts +5 -0
  110. package/dist/web/primitives/data-table/export-button.js +37 -0
  111. package/dist/web/primitives/data-table/facet-cluster.d.ts +8 -0
  112. package/dist/web/primitives/data-table/facet-cluster.js +37 -0
  113. package/dist/web/primitives/data-table/group-band.d.ts +28 -0
  114. package/dist/web/primitives/data-table/group-band.js +53 -0
  115. package/dist/web/primitives/data-table/measure-value.d.ts +10 -0
  116. package/dist/web/primitives/data-table/measure-value.js +28 -0
  117. package/dist/web/primitives/data-table/pin-section.d.ts +8 -0
  118. package/dist/web/primitives/data-table/pin-section.js +20 -0
  119. package/dist/web/primitives/data-table/reset-all-button.d.ts +4 -0
  120. package/dist/web/primitives/data-table/reset-all-button.js +8 -0
  121. package/dist/web/primitives/data-table/server-group-header.d.ts +11 -0
  122. package/dist/web/primitives/data-table/server-group-header.js +51 -0
  123. package/dist/web/primitives/data-table/sortable-header.d.ts +17 -0
  124. package/dist/web/primitives/data-table/sortable-header.js +41 -0
  125. package/dist/web/primitives/data-table/table-chrome.d.ts +13 -0
  126. package/dist/web/primitives/data-table/table-chrome.js +30 -0
  127. package/dist/web/primitives/data-table/table-footer.d.ts +7 -0
  128. package/dist/web/primitives/data-table/table-footer.js +20 -0
  129. package/dist/web/primitives/data-table/toolbar-end-cluster.d.ts +10 -0
  130. package/dist/web/primitives/data-table/toolbar-end-cluster.js +13 -0
  131. package/dist/web/primitives/data-table/totals-footer.d.ts +24 -0
  132. package/dist/web/primitives/data-table/totals-footer.js +48 -0
  133. package/dist/web/primitives/data-table/use-bulk-selection.d.ts +21 -0
  134. package/dist/web/primitives/data-table/use-bulk-selection.js +66 -0
  135. package/dist/web/primitives/data-table/use-column-pins.d.ts +32 -0
  136. package/dist/web/primitives/data-table/use-column-pins.js +117 -0
  137. package/dist/web/primitives/data-table/use-column-sizing.d.ts +22 -0
  138. package/dist/web/primitives/data-table/use-column-sizing.js +44 -0
  139. package/dist/web/primitives/data-table/use-measured-column-widths.d.ts +15 -0
  140. package/dist/web/primitives/data-table/use-measured-column-widths.js +83 -0
  141. package/dist/web/primitives/data-table/use-row-keyboard-navigation.d.ts +22 -0
  142. package/dist/web/primitives/data-table/use-row-keyboard-navigation.js +65 -0
  143. package/dist/web/primitives/data-table/use-row-selection.d.ts +14 -0
  144. package/dist/web/primitives/data-table/use-row-selection.js +48 -0
  145. package/dist/web/primitives/data-table/use-view-columns.d.ts +6 -0
  146. package/dist/web/primitives/data-table/use-view-columns.js +17 -0
  147. package/dist/web/primitives/data-table/view-menu.d.ts +8 -0
  148. package/dist/web/primitives/data-table/view-menu.js +45 -0
  149. package/dist/web/primitives/data-table-cell.d.ts +0 -22
  150. package/dist/web/primitives/data-table-cell.js +1 -58
  151. package/dist/web/primitives/data-table.d.ts +1 -1
  152. package/dist/web/primitives/data-table.js +122 -144
  153. package/dist/web/primitives/file-value-list.d.ts +1 -1
  154. package/dist/web/primitives/file-value-list.js +5 -5
  155. package/dist/web/primitives/number-range-facet.d.ts +7 -0
  156. package/dist/web/primitives/number-range-facet.js +33 -0
  157. package/dist/web/widgets/bounded-load-cache.d.ts +5 -1
  158. package/dist/web/widgets/bounded-load-cache.js +48 -26
  159. package/dist/web/widgets/pdf-preview.js +9 -4
  160. package/package.json +5 -5
  161. package/src/changes.json +109 -0
@@ -1,6 +1,9 @@
1
1
  import type { ConfigCascade, ConfigScope, ConfigValueSource } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import type { FieldIconKey, FormWidth, IconKey, NavIconKey, RowActionDisplay, SelectOptionTone } from "@cosmicdrift/kumiko-framework/ui-types";
3
3
  import { type ComponentType, type FormEvent, type KeyboardEvent, type ReactNode, type Ref } from "react";
4
+ import type { DataTableBulk } from "./data-table-bulk-types.ts";
5
+ import type { DataTableDateRangeFacet, DataTableFacet } from "./data-table-facet-types.ts";
6
+ import type { DataTableTableBlock } from "./data-table-view-types.ts";
4
7
  import type { FieldIssue, ListColumnViewModel, ListRowViewModel, RuntimeRenderer } from "./headless/index.ts";
5
8
  export type { RuntimeRenderer };
6
9
  /** Escape hatch for `data-*` test hooks (e.g. an E2E selector) that don't
@@ -647,55 +650,11 @@ export type DataTableRowAction = {
647
650
  export declare function shouldRenderActionsIconOnly(actions: readonly {
648
651
  readonly icon?: IconKey;
649
652
  }[]): boolean;
650
- export type DataTableFacet = {
651
- /** Entity-Feldname (= URL-State-Key + payload.filters[].field). */
652
- readonly field: string;
653
- /** Translated Button-Label ("Status", "Active"). */
654
- readonly label: string;
655
- /** Selectable values with display labels. `value` is the raw filter value
656
- * (a select option or `"true"`/`"false"`), transported as a string. */
657
- readonly options: readonly {
658
- readonly value: string;
659
- readonly label: string;
660
- }[];
661
- /** Present: render the facet as a row of single-choice toggle chips instead
662
- * of a dropdown. A chip is pressed while the selection equals its values. */
663
- readonly chips?: readonly DataTableFacetChip[];
664
- /** Chips only: show each chip's `count` next to its label. */
665
- readonly showCounts?: boolean;
666
- /** Chips only: hide chips whose `count` is 0 (unless pressed). */
667
- readonly hideEmpty?: boolean;
668
- };
669
- export type DataTableFacetChip = {
670
- readonly id: string;
671
- /** Translated chip label. */
672
- readonly label: string;
673
- /** Selection a click sets; `[]` clears the facet. */
674
- readonly values: readonly string[];
675
- /** Matching row count, undefined while unknown. */
676
- readonly count?: number;
677
- };
678
- export type DataTableDateRangeFacet = {
679
- /** The facet's namespace (its date field) — a key, not a filter value. */
680
- readonly field: string;
681
- /** Translated label ("When"). */
682
- readonly label: string;
683
- readonly from: string;
684
- readonly to: string;
685
- };
686
- /** Splits rows into collapsible groups (order of first appearance). A
687
- * renderer without grouping support shows the flat list. A group whose
688
- * `headerLabel` is undefined shows its rows without a header and never
689
- * collapses. */
690
- export type DataTableRowGrouping = {
691
- readonly keyOf: (row: ListRowViewModel) => string;
692
- readonly headerLabel: (groupKey: string, rows: readonly ListRowViewModel[]) => string | undefined;
693
- readonly startsCollapsed: (groupKey: string) => boolean;
694
- };
653
+ export type { BulkRowOutcome, DataTableBulk, DataTableBulkAction, } from "./data-table-bulk-types.ts";
654
+ export type { DataTableDateRangeFacet, DataTableFacet, DataTableFacetChip, } from "./data-table-facet-types.ts";
695
655
  export type DataTableProps = {
696
656
  readonly columns: readonly ListColumnViewModel[];
697
657
  readonly rows: readonly ListRowViewModel[];
698
- readonly rowGrouping?: DataTableRowGrouping;
699
658
  /** Tints a row. Renderers without row tinting ignore it. */
700
659
  readonly rowTone?: (row: ListRowViewModel) => SelectOptionTone | undefined;
701
660
  readonly onRowClick?: (row: ListRowViewModel) => void;
@@ -723,6 +682,8 @@ export type DataTableProps = {
723
682
  * kebab). Actions then sit at the same x-position across all rows
724
683
  * (no drifting with label width) and all lists in an app look alike. */
725
684
  readonly rowActionMode?: DataTableRowActionMode;
685
+ /** Present: row checkboxes plus a selection bar that runs these actions once per selected row. */
686
+ readonly bulk?: DataTableBulk;
726
687
  /** Custom empty-state content (e.g. Icon + Heading + CTA-Button).
727
688
  * The default renderer wraps it in a dashed-border box. */
728
689
  readonly emptyState?: ReactNode;
@@ -832,7 +793,8 @@ export type DataTableProps = {
832
793
  /** Content of the area under an expanded row. Native renderers implement
833
794
  * the same three props. */
834
795
  readonly renderExpandedRow?: (row: ListRowViewModel) => ReactNode;
835
- };
796
+ } & DataTableTableBlock;
797
+ export type DataTableTableBlockProps = Pick<DataTableProps, keyof DataTableTableBlock>;
836
798
  /** Cell type for one column of an embedded-list field. Mirrors
837
799
  * EmbeddedListCellViewModel["type"] from `@cosmicdrift/kumiko-renderer/headless`. */
838
800
  export type EmbeddedListCellType = "text" | "number" | "boolean" | "date" | "money" | "decimal" | "select" | "reference" | "timestamp";
@@ -1403,6 +1365,8 @@ export type JsonViewProps = {
1403
1365
  * PDFs, an inline preview. Used for `renderer: { format: "file" }` on a detail field. */
1404
1366
  export type FileValueListProps = {
1405
1367
  readonly fileIds: readonly string[];
1368
+ /** Starts the inline PDF preview of the first file open. */
1369
+ readonly previewOpenByDefault?: boolean;
1406
1370
  readonly testId?: string;
1407
1371
  };
1408
1372
  /** Band above the tabs of a record detail: optional subtitle line plus the
@@ -92,6 +92,11 @@ export type CreateKumikoAppOptions = {
92
92
  readonly hasWorkspaces?: boolean;
93
93
  readonly features?: readonly FeatureSchema[];
94
94
  }) => NavApi;
95
+ /** URL prefix the app is mounted under (for example "/admin"). The default nav adapter reads
96
+ * and writes URLs relative to it, and the auth gates and pages, which sit outside the
97
+ * NavProvider, use it to match and build their paths. A custom `navAdapter` handles its own
98
+ * prefix. */
99
+ readonly basePath?: string;
95
100
  /** App-wide default width for form/detail screens that don't set their
96
101
  * own `layout.width`. Default "4xl" (unchanged behavior) — a
97
102
  * consumer app that wants e.g. bundled-feature screens to match its own
@@ -3,7 +3,7 @@ import { isOpenToAllGranted } from "@cosmicdrift/kumiko-framework/ui-types";
3
3
  import { createContext, useContext, useEffect, useMemo, useRef, useState, } from "react";
4
4
  import { createRoot } from "react-dom/client";
5
5
  import { rendererError, rendererWarn } from "../../dev-console.js";
6
- import { AppFeaturesProvider, ClientActionsProvider, ColumnRenderersProvider, ContentEditorsProvider, CustomScreensProvider, createSessionEndedSignal, DashboardBodyProvider, DispatcherProvider, DraftStorageProvider, ErrorBoundary, ExtensionSectionsProvider, hasDetailScreen, KumikoScreen, kumikoDefaultTranslations, LeaveGuardDialog, LiveEventsProvider, LocaleProvider, mergeTranslations, NavProvider, navigateWithReturnTo, PrimitivesProvider, qualifyScreenId, ScreenMissingState, SessionEndedSignalProvider, TokensProvider, toAppSchema, translationsByLocaleFromKeys, useNav, usePrimitives, useTranslation, withSessionEndedDetection, } from "../../index.js";
6
+ import { AppFeaturesProvider, BasePathProvider, ClientActionsProvider, ColumnRenderersProvider, ContentEditorsProvider, CustomScreensProvider, createSessionEndedSignal, DashboardBodyProvider, DispatcherProvider, DraftStorageProvider, ErrorBoundary, ExtensionSectionsProvider, hasDetailScreen, KumikoScreen, kumikoDefaultTranslations, LeaveGuardDialog, LiveEventsProvider, LocaleProvider, mergeTranslations, NavProvider, navigateWithReturnTo, PrimitivesProvider, qualifyScreenId, ScreenMissingState, SessionEndedSignalProvider, TokensProvider, toAppSchema, translationsByLocaleFromKeys, useNav, usePrimitives, useTranslation, withSessionEndedDetection, } from "../../index.js";
7
7
  import { createLiveDispatcher } from "../../live/index.js";
8
8
  import { lastSegment } from "../layout/nav-tree.js";
9
9
  import { clearFileMetaCache } from "../primitives/file-thumbnail.js";
@@ -19,6 +19,7 @@ import { stackWrappers } from "./client-plugin.js";
19
19
  import { WebDashboardBody } from "./dashboard-body.js";
20
20
  import { DocumentLangSync } from "./document-lang-sync.js";
21
21
  import { createBrowserDraftStorage } from "./draft-storage.js";
22
+ import { ListStateStoreBoot } from "./list-state-store-boot.js";
22
23
  import { useBrowserNavApi } from "./nav.js";
23
24
  import { NavProvidersProvider } from "./nav-providers-context.js";
24
25
  import { ResolversProvider } from "./resolvers-context.js";
@@ -237,7 +238,7 @@ function KumikoAppRoot(props) {
237
238
  warnMissingCustomScreens(loaded, props.customScreens);
238
239
  setApp(loaded);
239
240
  }, schemaIsRoleProjected: isFetchMode, ...(isFetchMode && { onUnmountResetSchema: () => setApp(undefined) }), ...(props.translate !== undefined && { translate: props.translate }), ...(props.onRowClick !== undefined && { onRowClick: props.onRowClick }), ...(props.shell !== undefined && { shell: props.shell }) }));
240
- return (_jsx(TokensBoot, { children: _jsxs(LocaleProvider, { resolver: props.localeResolver, fallbackBundles: fallbackBundles, children: [_jsx(DocumentLangSync, { resolver: props.localeResolver }), _jsx(PrimitivesProvider, { value: props.primitives, children: _jsx(ScreenWidthProvider, { width: props.screenWidth, children: _jsx(AppFeaturesProvider, { features: app?.features ?? EMPTY_FEATURES, children: _jsx(DispatcherProvider, { dispatcher: props.dispatcher, children: _jsx(DraftStorageProvider, { value: props.draftStorage, children: _jsx(LiveEventsProvider, { value: props.liveEvents, children: _jsx(DashboardBodyProvider, { value: WebDashboardBody, children: _jsx(CustomScreensProvider, { value: props.customScreens, children: _jsx(ColumnRenderersProvider, { value: props.columnRenderers, children: _jsx(ContentEditorsProvider, { value: props.contentEditors, children: _jsx(ExtensionSectionsProvider, { value: props.extensionSectionComponents, children: _jsx(ClientActionsProvider, { value: props.clientActions, children: _jsx(NavProvidersProvider, { value: navProviders, entities: navEntities, children: _jsx(ResolversProvider, { resolvers: props.resolvers, children: _jsx(ToastProvider, { children: _jsxs(ToastActionNotifier, { children: [_jsx(UpdateChecker, {}), _jsx(LeaveGuardDialog, {}), _jsx(SessionEndedSignalProvider, { signal: props.sessionEndedSignal, children: stackWrappers(props.providers, stackWrappers(props.gates, screenNode)) })] }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) })] }) }));
241
+ return (_jsx(TokensBoot, { children: _jsxs(LocaleProvider, { resolver: props.localeResolver, fallbackBundles: fallbackBundles, children: [_jsx(DocumentLangSync, { resolver: props.localeResolver }), _jsx(PrimitivesProvider, { value: props.primitives, children: _jsx(ScreenWidthProvider, { width: props.screenWidth, children: _jsx(AppFeaturesProvider, { features: app?.features ?? EMPTY_FEATURES, children: _jsx(DispatcherProvider, { dispatcher: props.dispatcher, children: _jsx(DraftStorageProvider, { value: props.draftStorage, children: _jsx(LiveEventsProvider, { value: props.liveEvents, children: _jsx(DashboardBodyProvider, { value: WebDashboardBody, children: _jsx(CustomScreensProvider, { value: props.customScreens, children: _jsx(ColumnRenderersProvider, { value: props.columnRenderers, children: _jsx(ContentEditorsProvider, { value: props.contentEditors, children: _jsx(ExtensionSectionsProvider, { value: props.extensionSectionComponents, children: _jsx(ClientActionsProvider, { value: props.clientActions, children: _jsx(NavProvidersProvider, { value: navProviders, entities: navEntities, children: _jsx(ResolversProvider, { resolvers: props.resolvers, children: _jsx(ToastProvider, { children: _jsxs(ToastActionNotifier, { children: [_jsx(UpdateChecker, {}), _jsx(LeaveGuardDialog, {}), _jsx(SessionEndedSignalProvider, { signal: props.sessionEndedSignal, children: stackWrappers(props.providers, stackWrappers(props.gates, _jsx(ListStateStoreBoot, { children: screenNode }))) })] }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) })] }) }));
241
242
  }
242
243
  // Sits INSIDE the clientFeature gates (e.g. an auth gate) — a gate that
243
244
  // renders a login placeholder instead of children keeps this component
@@ -340,7 +341,9 @@ export function createKumikoApp(options = {}) {
340
341
  }
341
342
  }
342
343
  const localeResolver = options.locale ?? createBrowserLocaleResolver();
343
- const navAdapter = options.navAdapter ?? useBrowserNavApi;
344
+ const { basePath } = options;
345
+ const useBrowserNavApiWithBasePath = (navOptions) => useBrowserNavApi({ ...navOptions, ...(basePath !== undefined && { basePath }) });
346
+ const navAdapter = options.navAdapter ?? useBrowserNavApiWithBasePath;
344
347
  const reporter = options.reportClientErrors === false ? undefined : createClientErrorReporter();
345
348
  reporter?.installGlobalHandlers();
346
349
  const handleBoundaryError = (error, info, context) => {
@@ -353,7 +356,7 @@ export function createKumikoApp(options = {}) {
353
356
  options.onError?.(error, info, context);
354
357
  };
355
358
  const root = createRoot(container);
356
- root.render(_jsx(ErrorBoundary, { scope: "root", onError: handleBoundaryError, fallback: ({ errorId }) => _jsx(RootErrorFallback, { errorId: errorId }), children: _jsx(BoundaryErrorHandlerContext.Provider, { value: handleBoundaryError, children: _jsx(KumikoAppRoot, { initialApp: initialApp, screenQn: options.screenQn, ...(options.schemaUrl !== undefined && { schemaUrl: options.schemaUrl }), clientFeatures: clientFeatures, providers: providers, gates: gates, customScreens: customScreens, columnRenderers: columnRenderers, extensionSectionComponents: extensionSectionComponents, clientActions: clientActions, contentEditors: contentEditors, resolvers: resolvers, dispatcher: dispatcher, sessionEndedSignal: sessionEndedSignal, draftStorage: draftStorage, primitives: primitives, liveEvents: liveEvents, localeResolver: localeResolver, navAdapter: navAdapter, screenWidth: options.screenWidth, ...(options.translate !== undefined && { translate: options.translate }), ...(options.onRowClick !== undefined && { onRowClick: options.onRowClick }), ...(options.shell !== undefined && { shell: options.shell }) }) }) }));
359
+ root.render(_jsx(ErrorBoundary, { scope: "root", onError: handleBoundaryError, fallback: ({ errorId }) => _jsx(RootErrorFallback, { errorId: errorId }), children: _jsx(BoundaryErrorHandlerContext.Provider, { value: handleBoundaryError, children: _jsx(BasePathProvider, { basePath: basePath, children: _jsx(KumikoAppRoot, { initialApp: initialApp, screenQn: options.screenQn, ...(options.schemaUrl !== undefined && { schemaUrl: options.schemaUrl }), clientFeatures: clientFeatures, providers: providers, gates: gates, customScreens: customScreens, columnRenderers: columnRenderers, extensionSectionComponents: extensionSectionComponents, clientActions: clientActions, contentEditors: contentEditors, resolvers: resolvers, dispatcher: dispatcher, sessionEndedSignal: sessionEndedSignal, draftStorage: draftStorage, primitives: primitives, liveEvents: liveEvents, localeResolver: localeResolver, navAdapter: navAdapter, screenWidth: options.screenWidth, ...(options.translate !== undefined && { translate: options.translate }), ...(options.onRowClick !== undefined && { onRowClick: options.onRowClick }), ...(options.shell !== undefined && { shell: options.shell }) }) }) }) }));
357
360
  return { root };
358
361
  }
359
362
  // TokensBoot uses the browser-backed TokensApi hook (class-based
@@ -0,0 +1,6 @@
1
+ export declare function committedUrl(): string;
2
+ /** Adopts the entry the browser currently shows as the committed one. */
3
+ export declare function adoptCurrentEntry(): void;
4
+ export declare function pushEntry(url: string): void;
5
+ export declare function replaceEntry(url: string): void;
6
+ export declare function handlePopstate(notify: () => void): void;
@@ -0,0 +1,115 @@
1
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
2
+ import { hasUnsavedInput, requestLeave } from "../../index.js";
3
+ // Back/Forward cannot be vetoed, so the browser has already moved by the time the guard asks.
4
+ // Each entry we create carries its position in history.state; with it a declined move is
5
+ // undone with history.go(-delta) and a confirmed one replayed with history.go(delta), so the
6
+ // stack keeps its shape (no duplicated or truncated entries).
7
+ const INDEX_KEY = "kumikoNavIndex";
8
+ const committed = { url: "", index: 0 };
9
+ let leaveRequestOpen = false;
10
+ // Each history.go() we issue produces one popstate that must not be treated as user navigation.
11
+ const pendingMoves = [];
12
+ // Entries without a position (written by other code) cannot be undone relatively.
13
+ let fallbackTargetUrl;
14
+ function readUrl() {
15
+ return `${window.location.pathname}${window.location.search}${window.location.hash}`;
16
+ }
17
+ function stateAt(index) {
18
+ return { [INDEX_KEY]: index };
19
+ }
20
+ function readIndex() {
21
+ const value = propertyOf(window.history.state, INDEX_KEY);
22
+ return typeof value === "number" ? value : undefined;
23
+ }
24
+ export function committedUrl() {
25
+ return committed.url;
26
+ }
27
+ /** Adopts the entry the browser currently shows as the committed one. */
28
+ export function adoptCurrentEntry() {
29
+ const index = readIndex();
30
+ committed.url = readUrl();
31
+ if (index === undefined) {
32
+ window.history.replaceState(stateAt(committed.index), "", committed.url);
33
+ }
34
+ else {
35
+ committed.index = index;
36
+ }
37
+ }
38
+ function commitCurrentUrl() {
39
+ committed.url = readUrl();
40
+ committed.index = readIndex() ?? committed.index;
41
+ }
42
+ export function pushEntry(url) {
43
+ window.history.pushState(stateAt(committed.index + 1), "", url);
44
+ committed.index += 1;
45
+ committed.url = readUrl();
46
+ }
47
+ export function replaceEntry(url) {
48
+ window.history.replaceState(stateAt(committed.index), "", url);
49
+ commitCurrentUrl();
50
+ }
51
+ export function handlePopstate(notify) {
52
+ const move = pendingMoves.shift();
53
+ if (move !== undefined) {
54
+ if (move === "replay") {
55
+ commitCurrentUrl();
56
+ notify();
57
+ }
58
+ // skip: this popstate is the echo of our own history.go()
59
+ return;
60
+ }
61
+ if (fallbackTargetUrl !== undefined) {
62
+ const target = fallbackTargetUrl;
63
+ fallbackTargetUrl = undefined;
64
+ // After a forward move the entry below our restore-push is not the target.
65
+ if (readUrl() !== target)
66
+ window.history.replaceState(stateAt(committed.index), "", target);
67
+ commitCurrentUrl();
68
+ notify();
69
+ // skip: the pending fallback move is settled
70
+ return;
71
+ }
72
+ const newIndex = readIndex();
73
+ const delta = newIndex === undefined ? 0 : newIndex - committed.index;
74
+ if (leaveRequestOpen) {
75
+ // Another Back/Forward while the dialog is open must neither stack an entry nor replace the
76
+ // open request: step back to the entry the dialog is about.
77
+ if (delta !== 0) {
78
+ pendingMoves.push("undo");
79
+ window.history.go(-delta);
80
+ }
81
+ else {
82
+ window.history.replaceState(stateAt(committed.index), "", committed.url);
83
+ }
84
+ // skip: the open leave dialog keeps the decision
85
+ return;
86
+ }
87
+ if (!hasUnsavedInput()) {
88
+ commitCurrentUrl();
89
+ notify();
90
+ // skip: nothing unsaved, the move stands
91
+ return;
92
+ }
93
+ const targetUrl = readUrl();
94
+ leaveRequestOpen = true;
95
+ const settle = () => {
96
+ leaveRequestOpen = false;
97
+ };
98
+ if (delta !== 0) {
99
+ pendingMoves.push("undo");
100
+ window.history.go(-delta);
101
+ requestLeave(() => {
102
+ settle();
103
+ pendingMoves.push("replay");
104
+ window.history.go(delta);
105
+ }, settle);
106
+ // skip: the leave request owns the rest of the move
107
+ return;
108
+ }
109
+ window.history.pushState(stateAt(committed.index), "", committed.url);
110
+ requestLeave(() => {
111
+ settle();
112
+ fallbackTargetUrl = targetUrl;
113
+ window.history.back();
114
+ }, settle);
115
+ }
@@ -0,0 +1,6 @@
1
+ import { type ReactNode } from "react";
2
+ /** Provides the tab-scoped list-state store and drops state remembered under
3
+ * another user or tenant. Mount below the auth gates so the user is known. */
4
+ export declare function ListStateStoreBoot({ children }: {
5
+ readonly children: ReactNode;
6
+ }): ReactNode;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { ListStateStoreProvider } from "../../components/list-state-store.js";
4
+ import { useOptionalCurrentUser } from "../../current-user.js";
5
+ import { resolveListStateStore } from "./session-list-state-store.js";
6
+ /** Provides the tab-scoped list-state store and drops state remembered under
7
+ * another user or tenant. Mount below the auth gates so the user is known. */
8
+ export function ListStateStoreBoot({ children }) {
9
+ const user = useOptionalCurrentUser();
10
+ const userId = user?.id;
11
+ const tenantId = user?.tenantId;
12
+ // Must run before the first list renders; an effect would let it read the previous identity's state.
13
+ const store = useMemo(() => resolveListStateStore(userId === undefined ? undefined : { userId, tenantId }), [userId, tenantId]);
14
+ return _jsx(ListStateStoreProvider, { store: store, children: children });
15
+ }
@@ -12,7 +12,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
12
12
  // notification triggers (our pushPath and the popstate event), so this is not
13
13
  // generalizable.
14
14
  import { useCallback, useMemo, useSyncExternalStore, } from "react";
15
+ import { normalizeBasePath, prependBasePath, stripBasePath } from "../../app/base-path.js";
15
16
  import { formatPath, hasUnsavedInput, parsePath, requestLeave, resolveTarget, useNav, } from "../../index.js";
17
+ import { adoptCurrentEntry, handlePopstate, pushEntry, replaceEntry, } from "./history-leave-guard.js";
16
18
  // basePath allows the app to be mounted under a URL prefix
17
19
  // (for example, `/admin`). The read path strips the prefix before parsePath;
18
20
  // the write path prepends it before pushState/replaceState/href.
@@ -22,45 +24,11 @@ import { formatPath, hasUnsavedInput, parsePath, requestLeave, resolveTarget, us
22
24
  // returns route=undefined, and the app renders its "outside" state
23
25
  // (Not-Found, marketing layer, server-routing pickup, …). There is no
24
26
  // automatic redirect to the app root—the host app decides.
25
- function normalizeBasePath(raw) {
26
- if (raw === undefined || raw === "" || raw === "/")
27
- return "";
28
- const withLeading = raw.startsWith("/") ? raw : `/${raw}`;
29
- return withLeading.endsWith("/") ? withLeading.slice(0, -1) : withLeading;
30
- }
31
- // Returns the in-app (basePath-relative) path when `path` is under basePath;
32
- // otherwise returns `undefined`. The segment boundary is strict: "/administrator"
33
- // does not match "/admin"—otherwise a string-level prefix check would produce
34
- // false matches.
35
- function stripBasePath(path, basePath) {
36
- if (basePath === "")
37
- return path;
38
- if (path === basePath)
39
- return "/";
40
- if (path.startsWith(`${basePath}/`))
41
- return path.slice(basePath.length);
42
- return undefined;
43
- }
44
- function prependBasePath(path, basePath) {
45
- if (basePath === "")
46
- return path;
47
- // formatPath always returns an absolute in-app path: "/" or "/screen-id"
48
- // or "/screen-id/entity-id". "/" becomes basePath; otherwise, concatenate.
49
- if (path === "/" || path === "")
50
- return basePath;
51
- return `${basePath}${path}`;
52
- }
53
27
  // pushState does not fire popstate, so we maintain a listener set that
54
28
  // navigate() notifies. Back/Forward navigation goes through a window listener
55
29
  // wired exactly once.
56
30
  const listeners = new Set();
57
31
  let popstateWired = false;
58
- // URL the app currently shows; popstate has already moved the browser to the
59
- // target by the time we can ask, so a declined leave must restore this one.
60
- let committedUrl = "";
61
- function readUrl() {
62
- return `${window.location.pathname}${window.location.search}${window.location.hash}`;
63
- }
64
32
  // Stable identity for the omitted-features case — `?? []` would create a
65
33
  // fresh array every render, busting the useMemo below on every render for
66
34
  // every existing useBrowserNavApi() call site (none of which pass features).
@@ -71,19 +39,7 @@ function ensurePopstateWired() {
71
39
  if (typeof window === "undefined")
72
40
  return;
73
41
  window.addEventListener("popstate", () => {
74
- if (!hasUnsavedInput()) {
75
- committedUrl = readUrl();
76
- for (const l of listeners)
77
- l();
78
- return;
79
- }
80
- const targetUrl = readUrl();
81
- window.history.pushState(null, "", committedUrl);
82
- requestLeave(() => {
83
- // replaceState, not history.back(): the declined entry is already ours
84
- // and a forward navigation would otherwise land on the wrong entry.
85
- window.history.replaceState(null, "", targetUrl);
86
- committedUrl = targetUrl;
42
+ handlePopstate(() => {
87
43
  for (const l of listeners)
88
44
  l();
89
45
  });
@@ -99,7 +55,7 @@ function ensurePopstateWired() {
99
55
  function subscribe(listener) {
100
56
  ensurePopstateWired();
101
57
  if (typeof window !== "undefined" && listeners.size === 0)
102
- committedUrl = readUrl();
58
+ adoptCurrentEntry();
103
59
  listeners.add(listener);
104
60
  return () => {
105
61
  listeners.delete(listener);
@@ -146,8 +102,7 @@ function applySearchParamUpdates(updates) {
146
102
  if (nextSearch === window.location.search)
147
103
  return;
148
104
  const url = `${window.location.pathname}${nextSearch}${window.location.hash}`;
149
- window.history.replaceState(null, "", url);
150
- committedUrl = readUrl();
105
+ replaceEntry(url);
151
106
  for (const l of listeners)
152
107
  l();
153
108
  }
@@ -168,8 +123,7 @@ function pushPath(path, search) {
168
123
  if (unchanged)
169
124
  return;
170
125
  requestLeave(() => {
171
- window.history.pushState(null, "", `${path}${search ?? ""}`);
172
- committedUrl = readUrl();
126
+ pushEntry(`${path}${search ?? ""}`);
173
127
  for (const l of listeners)
174
128
  l();
175
129
  });
@@ -185,8 +139,7 @@ function replacePath(path, search) {
185
139
  const leavesCurrentPage = window.location.pathname !== path ||
186
140
  (search !== undefined && window.location.search !== search);
187
141
  const apply = () => {
188
- window.history.replaceState(null, "", nextUrl);
189
- committedUrl = readUrl();
142
+ replaceEntry(nextUrl);
190
143
  for (const l of listeners)
191
144
  l();
192
145
  };
@@ -0,0 +1,15 @@
1
+ import type { ListStateStore } from "../../components/list-state-store.tsx";
2
+ export type ListStateIdentity = {
3
+ readonly userId: string;
4
+ readonly tenantId?: string | undefined;
5
+ };
6
+ /** Remembered list state for the lifetime of the browser tab, keyed per tenant
7
+ * and user. Every storage failure (blocked, full) degrades to "nothing remembered". */
8
+ export declare function createSessionListStateStore(storage: Storage, identity: ListStateIdentity): ListStateStore;
9
+ /** Forgets all remembered list state and the identity marker. Logout calls it
10
+ * before a page reload, which would otherwise hide the identity change. */
11
+ export declare function clearRememberedListState(): void;
12
+ /** The store for the current identity. State remembered under another identity,
13
+ * or under one that has since gone away, is dropped first. Without an identity
14
+ * (app without auth) only the in-memory store is used and nothing is persisted. */
15
+ export declare function resolveListStateStore(identity: ListStateIdentity | undefined): ListStateStore;
@@ -0,0 +1,117 @@
1
+ import { getDefaultMemoryListStateStore } from "../../components/list-state-store.js";
2
+ import { parseStoredListStateJson } from "../../hooks/list-state-schema.js";
3
+ const KEY_PREFIX = "kumiko.list.";
4
+ const IDENTITY_KEY = `${KEY_PREFIX}identity`;
5
+ const NO_TENANT = "-";
6
+ function identityMarker(identity) {
7
+ return `${identity.tenantId ?? NO_TENANT}.${identity.userId}`;
8
+ }
9
+ function openSessionStorage() {
10
+ try {
11
+ return typeof window === "undefined" ? undefined : window.sessionStorage;
12
+ }
13
+ catch {
14
+ // Access itself throws when storage is blocked (privacy modes, sandboxed frames).
15
+ return undefined;
16
+ }
17
+ }
18
+ function prefixedKeys(storage) {
19
+ return Array.from({ length: storage.length }, (_, index) => storage.key(index)).filter((key) => key !== null && key.startsWith(KEY_PREFIX));
20
+ }
21
+ function removeAllRemembered(storage) {
22
+ for (const key of prefixedKeys(storage))
23
+ storage.removeItem(key);
24
+ }
25
+ /** Remembered list state for the lifetime of the browser tab, keyed per tenant
26
+ * and user. Every storage failure (blocked, full) degrades to "nothing remembered". */
27
+ export function createSessionListStateStore(storage, identity) {
28
+ const keyPrefix = `${KEY_PREFIX}${identityMarker(identity)}.`;
29
+ return {
30
+ read: (key) => {
31
+ try {
32
+ const raw = storage.getItem(`${keyPrefix}${key}`);
33
+ return raw === null ? undefined : parseStoredListStateJson(raw);
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ },
39
+ write: (key, state) => {
40
+ try {
41
+ storage.setItem(`${keyPrefix}${key}`, JSON.stringify(state));
42
+ }
43
+ catch {
44
+ // Quota or blocked storage: the list keeps working, it just is not remembered.
45
+ }
46
+ },
47
+ remove: (key) => {
48
+ try {
49
+ storage.removeItem(`${keyPrefix}${key}`);
50
+ }
51
+ catch {
52
+ // See write.
53
+ }
54
+ },
55
+ clear: () => {
56
+ try {
57
+ for (const key of prefixedKeys(storage).filter((k) => k !== IDENTITY_KEY)) {
58
+ storage.removeItem(key);
59
+ }
60
+ }
61
+ catch {
62
+ // See write.
63
+ }
64
+ },
65
+ };
66
+ }
67
+ /** Forgets all remembered list state and the identity marker. Logout calls it
68
+ * before a page reload, which would otherwise hide the identity change. */
69
+ export function clearRememberedListState() {
70
+ getDefaultMemoryListStateStore().clear();
71
+ lastMemoryIdentity = undefined;
72
+ const storage = openSessionStorage();
73
+ // skip: no sessionStorage available, memory store already cleared
74
+ if (storage === undefined)
75
+ return;
76
+ try {
77
+ removeAllRemembered(storage);
78
+ }
79
+ catch {
80
+ // See createSessionListStateStore.
81
+ }
82
+ }
83
+ // The in-memory store survives SPA identity changes, so it needs its own marker.
84
+ let lastMemoryIdentity;
85
+ /** The store for the current identity. State remembered under another identity,
86
+ * or under one that has since gone away, is dropped first. Without an identity
87
+ * (app without auth) only the in-memory store is used and nothing is persisted. */
88
+ export function resolveListStateStore(identity) {
89
+ const storage = openSessionStorage();
90
+ const memory = getDefaultMemoryListStateStore();
91
+ const marker = identity === undefined ? undefined : identityMarker(identity);
92
+ if (lastMemoryIdentity !== marker) {
93
+ if (lastMemoryIdentity !== undefined)
94
+ memory.clear();
95
+ lastMemoryIdentity = marker;
96
+ }
97
+ if (storage === undefined)
98
+ return memory;
99
+ try {
100
+ const storedMarker = storage.getItem(IDENTITY_KEY);
101
+ if (marker === undefined) {
102
+ if (storedMarker !== null) {
103
+ removeAllRemembered(storage);
104
+ memory.clear();
105
+ }
106
+ return memory;
107
+ }
108
+ if (storedMarker !== marker) {
109
+ removeAllRemembered(storage);
110
+ storage.setItem(IDENTITY_KEY, marker);
111
+ }
112
+ }
113
+ catch {
114
+ return memory;
115
+ }
116
+ return identity === undefined ? memory : createSessionListStateStore(storage, identity);
117
+ }
@@ -13,6 +13,7 @@ export { KumikoLink, useBrowserNavApi } from "./app/nav.tsx";
13
13
  export { PlainContentEditor } from "./app/plain-content-editor.tsx";
14
14
  export { useResolvers } from "./app/resolvers-context.tsx";
15
15
  export { RichContentEditor } from "./app/rich-content-editor.tsx";
16
+ export { clearRememberedListState } from "./app/session-list-state-store.ts";
16
17
  export type { NavIconKey } from "./icons.tsx";
17
18
  export { Icon } from "./icons.tsx";
18
19
  export type { AppLayoutProps } from "./layout/app-layout.tsx";
package/dist/web/index.js CHANGED
@@ -12,6 +12,7 @@ export { KumikoLink, useBrowserNavApi } from "./app/nav.js";
12
12
  export { PlainContentEditor } from "./app/plain-content-editor.js";
13
13
  export { useResolvers } from "./app/resolvers-context.js";
14
14
  export { RichContentEditor } from "./app/rich-content-editor.js";
15
+ export { clearRememberedListState } from "./app/session-list-state-store.js";
15
16
  export { Icon } from "./icons.js";
16
17
  export { AppLayout } from "./layout/app-layout.js";
17
18
  export { Avatar } from "./layout/avatar.js";
@@ -0,0 +1,11 @@
1
+ import type { DataTableExport } from "../../data-table-view-types.ts";
2
+ export type XlsxExportResult = {
3
+ readonly status: "ok";
4
+ readonly blob: Blob;
5
+ } | {
6
+ readonly status: "too-many";
7
+ readonly maxRows: number | undefined;
8
+ } | {
9
+ readonly status: "failed";
10
+ };
11
+ export declare function requestXlsxExport(source: DataTableExport): Promise<XlsxExportResult>;
@@ -0,0 +1,46 @@
1
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
2
+ import { CSRF_HEADER_NAME, LOCALE_HEADER_NAME, readActiveLocale, readCsrfToken, } from "../../live/index.js";
3
+ const TOO_MANY_ROWS_HTTP_STATUS = 413;
4
+ async function readMaxRows(res) {
5
+ try {
6
+ const body = await res.json();
7
+ const error = isPlainObject(body) ? body["error"] : undefined;
8
+ const details = isPlainObject(error) ? error["details"] : undefined;
9
+ const maxRows = isPlainObject(details) ? details["maxRows"] : undefined;
10
+ return typeof maxRows === "number" ? maxRows : undefined;
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ export async function requestXlsxExport(source) {
17
+ const headers = { "Content-Type": "application/json" };
18
+ const csrf = readCsrfToken();
19
+ if (csrf !== undefined)
20
+ headers[CSRF_HEADER_NAME] = csrf;
21
+ const locale = readActiveLocale();
22
+ if (locale !== undefined)
23
+ headers[LOCALE_HEADER_NAME] = locale;
24
+ try {
25
+ const res = await fetch("/api/export/xlsx", {
26
+ method: "POST",
27
+ credentials: "include",
28
+ headers,
29
+ body: JSON.stringify({
30
+ screen: source.screen,
31
+ payload: source.payload,
32
+ columns: source.columns,
33
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
34
+ }),
35
+ });
36
+ if (res.ok)
37
+ return { status: "ok", blob: await res.blob() };
38
+ if (res.status === TOO_MANY_ROWS_HTTP_STATUS) {
39
+ return { status: "too-many", maxRows: await readMaxRows(res) };
40
+ }
41
+ return { status: "failed" };
42
+ }
43
+ catch {
44
+ return { status: "failed" };
45
+ }
46
+ }
@@ -0,0 +1,16 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ export declare const ACTIONS_CONTENT_ATTRIBUTE = "data-actions-content";
3
+ export declare function stickyActionsCellClass(fillsHeight: boolean): string;
4
+ export declare function ActionsHeaderCell({ label, style, absorbsLeftover, }: {
5
+ readonly label: string;
6
+ readonly style?: CSSProperties | undefined;
7
+ /** Fixed table layout: `w-px` would pin this column to 1px, so it must size as auto and take the leftover width. */
8
+ readonly absorbsLeftover: boolean;
9
+ }): ReactNode;
10
+ export declare function ActionsBodyCell({ testId, fillsHeight, measureContent, children, }: {
11
+ readonly testId: string;
12
+ readonly fillsHeight: boolean;
13
+ /** Wraps the content so the column's natural width stays measurable under a fixed table layout. */
14
+ readonly measureContent: boolean;
15
+ readonly children: ReactNode;
16
+ }): ReactNode;