@krak-stack/registry 0.1.40 → 0.1.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,41 +6,19 @@ Tree-shakable runtime components and Effect services from the KrakStack shadcn r
6
6
  bun add @krak-stack/registry
7
7
  ```
8
8
 
9
- ## Temporary Effect form patches
9
+ ## Effect forms
10
10
 
11
- This interim release supports stable Effect 4, but the published form betas still
12
- use removed Effect import paths. Applications using Effect forms must apply both
13
- compatibility patches at their package-manager root; installing Registry alone
14
- does not apply them automatically.
15
-
16
- After installing Registry, copy its bundled patches into your application:
11
+ Applications using Effect forms need `@lucas-barake/effect-form@1.1.0` and
12
+ `@lucas-barake/effect-form-react@1.1.0`, which support stable Effect 4 without
13
+ compatibility patches:
17
14
 
18
15
  ```sh
19
- mkdir -p patches
20
- cp node_modules/@krak-stack/registry/patches/*.patch patches/
16
+ bun add @lucas-barake/effect-form@1.1.0 @lucas-barake/effect-form-react@1.1.0
21
17
  ```
22
18
 
23
- Merge these fields into the application's root `package.json`, preserving any
24
- existing overrides and patches:
25
-
26
- ```json
27
- {
28
- "overrides": {
29
- "@lucas-barake/effect-form": "0.25.0-beta.6",
30
- "@lucas-barake/effect-form-react": "0.26.0-beta.5"
31
- },
32
- "patchedDependencies": {
33
- "@lucas-barake/effect-form@0.25.0-beta.6": "patches/@lucas-barake%2Feffect-form@0.25.0-beta.6.patch",
34
- "@lucas-barake/effect-form-react@0.26.0-beta.5": "patches/@lucas-barake%2Feffect-form-react@0.26.0-beta.5.patch"
35
- }
36
- }
37
- ```
38
-
39
- Run `bun install` and commit the patches, manifest, and lockfile. Other package
40
- managers require equivalent patch configuration. See the bundled
41
- `patches/README.md` for provenance and removal instructions after
42
- [upstream release PR #111](https://github.com/lucas-barake/effect-form/pull/111)
43
- is published.
19
+ When upgrading from the patched betas, remove their `patchedDependencies`
20
+ entries and patch files, update any form version overrides to `1.1.0`, and run
21
+ `bun install`. Preserve unrelated dependency patches.
44
22
 
45
23
  ## Usage
46
24
 
@@ -79,25 +57,192 @@ import {
79
57
  import { NotificationService } from "@krak-stack/registry/service-notification";
80
58
  ```
81
59
 
82
- Inject API telemetry and browser proxy routes into the application HTTP layer:
60
+ ## OpenTelemetry
61
+
62
+ | Export | Purpose |
63
+ | --------------------------------------------------------- | --------------------------------------------------------------------------------------- |
64
+ | `HttpApiOtlp.layer` from `opentelemetry/api` | Server exporters and same-origin browser ingestion routes. Includes `ServerOtlp.layer`. |
65
+ | `ServerOtlp.layer` from `opentelemetry/server` | Exporters for workers, workflows, and CLIs without an HTTP server. |
66
+ | `BrowserOtlp.layer(options)` from `opentelemetry/browser` | Browser tracing and optional events, logs, and metrics. |
67
+
68
+ ### Setup
69
+
70
+ Provide the HTTP layer once at the application composition root:
83
71
 
84
72
  ```ts
73
+ import { Layer } from "effect";
74
+ import { HttpApiOtlp } from "@krak-stack/registry/opentelemetry/api";
75
+
85
76
  const appLayer = appRoutes.pipe(Layer.provideMerge(HttpApiOtlp.layer));
86
77
  ```
87
78
 
88
- Install browser telemetry globally on the Atom runtime used by the API client:
79
+ Configure an OTLP/HTTP collector on the server:
80
+
81
+ ```dotenv
82
+ OTEL_SERVICE_NAME=my-app-api
83
+ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
84
+ OTEL_TRACES_EXPORTER=otlp
85
+ OTEL_METRICS_EXPORTER=otlp
86
+ OTEL_LOGS_EXPORTER=otlp
87
+ ```
88
+
89
+ Browser events require the logs signal. The proxy forwards enabled signals from
90
+ `/api/otel/v1/{traces,metrics,logs}` to the collector. Keep collector credentials
91
+ server-side. Server log endpoint/header overrides use
92
+ `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` / `OTEL_EXPORTER_OTLP_LOGS_HEADERS`, falling
93
+ back to the shared endpoint/headers; these overrides do not change proxy routing.
94
+ `OTEL_SDK_DISABLED=true` disables server export, not browser collection.
95
+
96
+ Install one browser layer on the Atom context passed to `AtomHttpApi.Service`:
89
97
 
90
98
  ```ts
91
99
  import { Atom } from "effect/reactivity";
92
100
  import { BrowserOtlp } from "@krak-stack/registry/opentelemetry/browser";
93
101
 
94
102
  const apiRuntime = Atom.context();
95
-
96
103
  if (!import.meta.env.SSR) {
97
- apiRuntime.addGlobalLayer(BrowserOtlp.layer({ serviceName: "my-app-web" }));
104
+ apiRuntime.addGlobalLayer(
105
+ BrowserOtlp.layer({ serviceName: "my-app-web", events: {} }),
106
+ );
98
107
  }
99
108
  ```
100
109
 
110
+ Mount/read `ApiClient.runtime` in the root layout to start it. Browser methods
111
+ target the active layer even when run from another Effect runtime. Calls before
112
+ startup or after disposal are no-ops, not buffered. Dispose long-lived server and
113
+ browser runtimes on shutdown to flush pending data.
114
+
115
+ ### Browser API
116
+
117
+ All methods return Effects. Use `yield*` inside Effect code or
118
+ `Effect.runPromise(...)` / `Effect.runFork(...)` from application callbacks.
119
+
120
+ | Method | Behavior |
121
+ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
122
+ | `identify({ userId?, organizationId? })` | Replace identity for future events; omitted fields clear old values. Emits no event. |
123
+ | `identify(null)` | Clear identity, for example on logout. |
124
+ | `track(name, properties?, options?)` | Record a named event. |
125
+ | `page(options?)` | Record `browser.page_view`. Accepts `path`, `name`, `category`, `properties`, and event `options`. `pageView` is an alias. |
126
+
127
+ `identify` has no typed failure: invalid input warns without including supplied
128
+ values and leaves identity unchanged. `track` and `page` fail with an Effect
129
+ schema error on invalid input. Identity is in memory only, applies to events
130
+ rather than automatic traces/logs, and never rewrites queued events.
131
+
132
+ ### Auth example
133
+
134
+ Mount this once under the application's Atom registry. `ApiClient` and
135
+ `authSessionAtom` are application-owned; the telemetry package has no auth
136
+ dependency. The session atom in this example exposes KrakStack Auth session data.
137
+
138
+ ```tsx
139
+ import { useAtomSubscribe, useAtomValue } from "@effect/atom-react";
140
+ import { BrowserOtlp } from "@krak-stack/registry/opentelemetry/browser";
141
+ import { Effect } from "effect";
142
+ import { AsyncResult } from "effect/reactivity";
143
+ import { ApiClient } from "@/lib/api-client";
144
+ import { authSessionAtom } from "@/services/auth/client";
145
+
146
+ export const AuthTelemetry = () => {
147
+ const runtime = useAtomValue(ApiClient.runtime);
148
+
149
+ useAtomSubscribe(
150
+ authSessionAtom,
151
+ (session) => {
152
+ if (!AsyncResult.isSuccess(runtime)) return;
153
+
154
+ Effect.runFork(
155
+ BrowserOtlp.identify(
156
+ AsyncResult.isSuccess(session) && !session.waiting && session.value
157
+ ? {
158
+ userId: session.value.user.id,
159
+ organizationId:
160
+ session.value.session.activeOrganizationId ?? undefined,
161
+ }
162
+ : null,
163
+ ),
164
+ );
165
+ },
166
+ { immediate: true },
167
+ );
168
+
169
+ return null;
170
+ };
171
+ ```
172
+
173
+ This identifies after startup and clears identity during auth loading, failure,
174
+ and logout. The first page view may be anonymous. For other auth libraries, pass
175
+ the same two IDs or `null` when auth state changes. Apply your own consent policy;
176
+ do not send names, emails, or session tokens as identity.
177
+
178
+ ### Page views and browser options
179
+
180
+ `events: {}` enables custom events and one page view on startup, then on pathname
181
+ changes through `pushState`, `replaceState`, and back/forward navigation.
182
+ Same-path and query/hash-only changes are ignored. Tracking follows URL changes,
183
+ not loader completion. Paths exclude query strings/fragments but may still
184
+ contain personal IDs.
185
+
186
+ For sanitized route templates or custom timing, use
187
+ `events: { pageViews: false }` and call `page({ path: "/orders/:id" })` manually.
188
+ Do not combine automatic and manual tracking for the same view.
189
+
190
+ | Layer option | Default |
191
+ | ----------------------------------------- | --------------------------------------------------------------- |
192
+ | `serviceName` | Required |
193
+ | `traces` | `{}`; includes global exception capture. `false` disables both. |
194
+ | `events`, `logs`, `metrics` | `false`; enable each independently with `{}`. |
195
+ | `baseUrl` | `/api/otel` |
196
+ | `serviceVersion`, `attributes`, `headers` | Optional resource/transport metadata. |
197
+
198
+ Browser events batch every 30 seconds, up to 100 records by default. Override
199
+ `exportInterval`, `maxBatchSize`, and `shutdownTimeout` under `events`.
200
+
201
+ ### Server events and shared options
202
+
203
+ Define server events as Effects and run them inside the runtime with
204
+ `ServerOtlp.layer` (or the HTTP layer):
205
+
206
+ ```ts
207
+ import { Effect } from "effect";
208
+ import { ServerOtlp } from "@krak-stack/registry/opentelemetry/server";
209
+
210
+ const orderCompleted = Effect.gen(function* () {
211
+ yield* ServerOtlp.track("app.order.completed", { "order.total": 49 });
212
+ });
213
+ ```
214
+
215
+ Authenticated handlers inherit `user.id` and `organization.id` from
216
+ `AuthMiddleware` log annotations. Server identity is fiber-scoped; there is no
217
+ server `identify` method. Background jobs can supply identity per event.
218
+
219
+ Both `track` methods accept `options`:
220
+
221
+ | Option | OTLP attribute / meaning |
222
+ | ------------------------------------------------- | ------------------------------------------------------------------- |
223
+ | `userId`, `anonymousId`, `sessionId`, `messageId` | `user.id`, `analytics.anonymous_id`, `session.id`, `log.record.uid` |
224
+ | `context` | Additional flat attributes |
225
+ | `timestamp` | Effect `DateTime.Utc` occurrence time; otherwise recorded now |
226
+
227
+ Attribute precedence, lowest to highest: context, properties, explicit identity
228
+ options, browser identified identity, scoped log annotations. Values support
229
+ strings, finite numbers, booleans, and homogeneous arrays, not nested objects.
230
+ Use stable event names and put dynamic values in attributes. Message IDs support
231
+ correlation, not guaranteed deduplication.
232
+
233
+ Events are OTLP log records with `eventName`, INFO severity, and active trace/span
234
+ IDs when available; they do not create spans. `browser.page_view`, `url.path`,
235
+ `analytics.page.name`, and `analytics.page.category` describe page events; the
236
+ analytics names are project conventions, not standardized OTel semantics.
237
+
238
+ Configure collector/storage separately. Browser identity is untrusted analytics
239
+ metadata, not authorization or an audit trail. No visitor/session IDs or cookies
240
+ are generated. Browser flushing on hidden/pagehide is best-effort. Apply payload
241
+ and rate limits at ingestion, and keep sensitive data out of events and
242
+ high-cardinality fields out of Loki labels.
243
+
244
+ ## Other services
245
+
101
246
  `@krak-stack/registry/service-notification` is the source-compatible direct
102
247
  dispatcher. Use the separate `@krak-stack/notifications` package for a durable
103
248
  inbox, package-owned migrations, persisted delivery jobs, retries, workers, and
@@ -32,10 +32,10 @@ export interface DataTableListItemContext<TData> {
32
32
  }
33
33
  export interface DataTableListOptions {
34
34
  emptyLabel: ReactNode;
35
- display?: "list" | "icon";
36
- locale?: string;
37
- messages?: DataTableMessageTranslations;
38
- visibleCount?: number;
35
+ display?: "list" | "icon" | undefined;
36
+ locale?: string | undefined;
37
+ messages?: DataTableMessageTranslations | undefined;
38
+ visibleCount?: number | undefined;
39
39
  }
40
40
  export type DataTableListColumn<TData> = DataTableListOptions & {
41
41
  /** Includes items not loaded in the preview; the dropdown only shows supplied items. */
@@ -76,8 +76,8 @@ interface EditableDataTableListCellProps extends DataTableListOptions {
76
76
  totalCount?: number | undefined;
77
77
  }
78
78
  interface DataTableListCellActionsProps {
79
- actionsLabel?: string;
80
- itemActions?: readonly DataTableItemAction<DataTableListItem>[];
79
+ actionsLabel?: string | undefined;
80
+ itemActions?: readonly DataTableItemAction<DataTableListItem>[] | undefined;
81
81
  }
82
82
  export type DataTableListCellProps = DataTableListCellActionsProps & (EditableDataTableListCellProps | (DataTableListSummaryProps & {
83
83
  options?: never;
@@ -206,6 +206,23 @@ export type DataTablePaginationFeature = false | {
206
206
  rowCount: number;
207
207
  pageSizes?: readonly number[];
208
208
  };
209
+ export interface DataTableExportAction {
210
+ id: string;
211
+ label: string;
212
+ icon?: ReactNode;
213
+ disabled?: boolean;
214
+ /** The app owns fetching, authorization, field selection, and downloading. */
215
+ onExport: () => void | Promise<void>;
216
+ }
217
+ export interface DataTableExportFeature {
218
+ baseName: string;
219
+ /** Both scopes operate only on rows already supplied to the table. */
220
+ scope?: "currentPage" | "filteredRows";
221
+ /** Set false to offer only app-provided actions. Defaults to true. */
222
+ builtIn?: boolean;
223
+ actions?: readonly DataTableExportAction[];
224
+ onError?: (error: unknown, action: DataTableExportAction) => void;
225
+ }
209
226
  export interface DataTableFeatures<TData> {
210
227
  toolbar?: false | {
211
228
  /** Additional `MenubarMenu` controls rendered at the start of the toolbar. */
@@ -228,10 +245,7 @@ export interface DataTableFeatures<TData> {
228
245
  label?: string;
229
246
  items: readonly DataTableRowAction<TData>[];
230
247
  };
231
- export?: false | {
232
- baseName: string;
233
- scope?: "currentPage" | "filteredRows";
234
- };
248
+ export?: false | DataTableExportFeature;
235
249
  gallery?: false | {
236
250
  name: string;
237
251
  description?: string;
@@ -255,6 +269,7 @@ export type DataTableMessages = PaginationMessages & {
255
269
  export: string;
256
270
  exportCsv: string;
257
271
  exportJson: string;
272
+ exportFailed?: string;
258
273
  refresh: string;
259
274
  view: string;
260
275
  tableView: string;
@@ -295,6 +310,7 @@ export declare const dataTableMessages: (locale?: string, translations?: DataTab
295
310
  export: string;
296
311
  exportCsv: string;
297
312
  exportJson: string;
313
+ exportFailed: string;
298
314
  refresh: string;
299
315
  view: string;
300
316
  tableView: string;
@@ -332,6 +348,7 @@ export declare const dataTableMessages: (locale?: string, translations?: DataTab
332
348
  export: string;
333
349
  exportCsv: string;
334
350
  exportJson: string;
351
+ exportFailed: string;
335
352
  refresh: string;
336
353
  view: string;
337
354
  tableView: string;
@@ -993,6 +993,7 @@ var messages2 = {
993
993
  export: "Export",
994
994
  exportCsv: "CSV",
995
995
  exportJson: "JSON",
996
+ exportFailed: "Export failed. Please try again.",
996
997
  refresh: "Refresh",
997
998
  view: "View",
998
999
  tableView: "Table",
@@ -1024,6 +1025,7 @@ var messages2 = {
1024
1025
  export: "Exporter",
1025
1026
  exportCsv: "CSV",
1026
1027
  exportJson: "JSON",
1028
+ exportFailed: "L’exportation a échoué. Veuillez réessayer.",
1027
1029
  refresh: "Rafraîchir",
1028
1030
  view: "Affichage",
1029
1031
  tableView: "Tableau",
@@ -1586,7 +1588,7 @@ var DataTableRowActions = ({
1586
1588
  event.stopPropagation();
1587
1589
  action.onClick(row);
1588
1590
  },
1589
- variant: action.variant,
1591
+ variant: action.variant ?? "default",
1590
1592
  children: [
1591
1593
  action.icon,
1592
1594
  action.name
@@ -1641,6 +1643,9 @@ var DataTableToolbar = ({
1641
1643
  tableAtom
1642
1644
  }) => {
1643
1645
  const [store, setStore] = useTableAtom(tableAtom);
1646
+ const exportPending = useRef(false);
1647
+ const [exporting, setExporting] = useState(false);
1648
+ const [exportFailed, setExportFailed] = useState(false);
1644
1649
  const { features, labels } = store.config;
1645
1650
  const model = deriveModel(store);
1646
1651
  const selectedRows = getSelectedRows(store, model);
@@ -1653,6 +1658,27 @@ var DataTableToolbar = ({
1653
1658
  const update = (next) => changeState(setStore, next);
1654
1659
  const visibleBulkActions = bulkActions ? bulkActions.items.filter((action) => !action.visible || action.visible(selectedRows)) : [];
1655
1660
  const exportRows = features.export && features.export.scope === "filteredRows" ? model.filteredRows : model.pageRows;
1661
+ const exportFeature = features.export;
1662
+ const exportActions = exportFeature ? exportFeature.actions ?? [] : [];
1663
+ const builtInExport = exportFeature && exportFeature.builtIn !== false;
1664
+ const canExport = builtInExport && exportRows.length > 0 || exportActions.some((action) => !action.disabled);
1665
+ const runExport = async (action) => {
1666
+ if (exportPending.current || action.disabled)
1667
+ return;
1668
+ exportPending.current = true;
1669
+ setExporting(true);
1670
+ setExportFailed(false);
1671
+ try {
1672
+ await action.onExport();
1673
+ } catch (error) {
1674
+ setExportFailed(true);
1675
+ if (exportFeature)
1676
+ exportFeature.onError?.(error, action);
1677
+ } finally {
1678
+ exportPending.current = false;
1679
+ setExporting(false);
1680
+ }
1681
+ };
1656
1682
  return /* @__PURE__ */ jsxs8("div", {
1657
1683
  className: "flex flex-col gap-2 pb-4",
1658
1684
  children: [
@@ -1716,7 +1742,7 @@ var DataTableToolbar = ({
1716
1742
  /* @__PURE__ */ jsx14(MenubarSeparator, {}),
1717
1743
  visibleBulkActions.map((action) => /* @__PURE__ */ jsxs8(MenubarItem, {
1718
1744
  onClick: () => action.onClick(selectedRows),
1719
- variant: action.variant,
1745
+ variant: action.variant ?? "default",
1720
1746
  children: [
1721
1747
  action.icon,
1722
1748
  action.name
@@ -1786,12 +1812,15 @@ var DataTableToolbar = ({
1786
1812
  children: [
1787
1813
  /* @__PURE__ */ jsxs8(MenubarTrigger, {
1788
1814
  "aria-label": labels.export,
1789
- disabled: !exportRows.length,
1815
+ "aria-busy": exporting,
1816
+ disabled: exporting || !canExport,
1790
1817
  children: [
1791
- /* @__PURE__ */ jsx14(Download, {}),
1818
+ exporting ? /* @__PURE__ */ jsx14(RefreshCw, {
1819
+ className: "animate-spin"
1820
+ }) : /* @__PURE__ */ jsx14(Download, {}),
1792
1821
  /* @__PURE__ */ jsx14("span", {
1793
1822
  className: "hidden sm:inline",
1794
- children: labels.export
1823
+ children: exporting ? labels.loading : labels.export
1795
1824
  })
1796
1825
  ]
1797
1826
  }),
@@ -1803,23 +1832,38 @@ var DataTableToolbar = ({
1803
1832
  children: labels.export
1804
1833
  }),
1805
1834
  /* @__PURE__ */ jsx14(MenubarSeparator, {}),
1806
- /* @__PURE__ */ jsxs8(MenubarItem, {
1807
- onClick: () => downloadCsv(model.visibleColumns.map(columnLabel), exportRows.map((row) => model.visibleColumns.map((column) => String(getDataTableExportValue(row, column, store.config.locale) ?? ""))), features.export ? features.export.baseName : "table"),
1835
+ builtInExport ? /* @__PURE__ */ jsxs8(Fragment, {
1808
1836
  children: [
1809
- /* @__PURE__ */ jsx14(FileText, {}),
1810
- labels.exportCsv
1837
+ /* @__PURE__ */ jsxs8(MenubarItem, {
1838
+ disabled: exporting || !exportRows.length,
1839
+ onClick: () => downloadCsv(model.visibleColumns.map(columnLabel), exportRows.map((row) => model.visibleColumns.map((column) => String(getDataTableExportValue(row, column, store.config.locale) ?? ""))), features.export ? features.export.baseName : "table"),
1840
+ children: [
1841
+ /* @__PURE__ */ jsx14(FileText, {}),
1842
+ labels.exportCsv
1843
+ ]
1844
+ }),
1845
+ /* @__PURE__ */ jsxs8(MenubarItem, {
1846
+ disabled: exporting || !exportRows.length,
1847
+ onClick: () => downloadJson(exportRows.map((row) => Object.fromEntries(model.visibleColumns.map((column) => [
1848
+ column.id,
1849
+ getDataTableExportValue(row, column, store.config.locale)
1850
+ ]))), features.export ? features.export.baseName : "table"),
1851
+ children: [
1852
+ /* @__PURE__ */ jsx14(FileJson, {}),
1853
+ labels.exportJson
1854
+ ]
1855
+ })
1811
1856
  ]
1812
- }),
1813
- /* @__PURE__ */ jsxs8(MenubarItem, {
1814
- onClick: () => downloadJson(exportRows.map((row) => Object.fromEntries(model.visibleColumns.map((column) => [
1815
- column.id,
1816
- getDataTableExportValue(row, column, store.config.locale)
1817
- ]))), features.export ? features.export.baseName : "table"),
1857
+ }) : null,
1858
+ builtInExport && exportActions.length ? /* @__PURE__ */ jsx14(MenubarSeparator, {}) : null,
1859
+ exportActions.map((action) => /* @__PURE__ */ jsxs8(MenubarItem, {
1860
+ disabled: exporting || action.disabled,
1861
+ onClick: () => void runExport(action),
1818
1862
  children: [
1819
- /* @__PURE__ */ jsx14(FileJson, {}),
1820
- labels.exportJson
1863
+ action.icon,
1864
+ action.label
1821
1865
  ]
1822
- })
1866
+ }, action.id))
1823
1867
  ]
1824
1868
  })
1825
1869
  })
@@ -1827,7 +1871,12 @@ var DataTableToolbar = ({
1827
1871
  }) : null
1828
1872
  ]
1829
1873
  })
1830
- })
1874
+ }),
1875
+ exportFeature && exportFailed ? /* @__PURE__ */ jsx14("p", {
1876
+ role: "alert",
1877
+ className: "text-destructive text-sm",
1878
+ children: labels.exportFailed ?? dataTableMessages(store.config.locale).exportFailed
1879
+ }) : null
1831
1880
  ]
1832
1881
  });
1833
1882
  };
@@ -2293,7 +2342,7 @@ var GroupActions = ({
2293
2342
  children: /* @__PURE__ */ jsx14(DropdownMenuGroup, {
2294
2343
  children: visible.map((action) => /* @__PURE__ */ jsxs8(DropdownMenuItem, {
2295
2344
  onClick: () => action.onClick(groupId),
2296
- variant: action.variant,
2345
+ variant: action.variant ?? "default",
2297
2346
  children: [
2298
2347
  action.icon,
2299
2348
  " ",
@@ -2796,23 +2845,21 @@ var HydratedDataTable = ({
2796
2845
  setPersistedUiState,
2797
2846
  ...props
2798
2847
  }) => {
2799
- const transientUiAtomRef = useRef(null);
2800
- if (!transientUiAtomRef.current) {
2848
+ const [transientUiAtom] = useState(() => {
2801
2849
  const grouping = props.features?.grouping;
2802
- transientUiAtomRef.current = Atom.make({
2850
+ return Atom.make({
2803
2851
  ...transientUiFromState(DEFAULT_UI_STATE),
2804
2852
  grouping: grouping && grouping.initial ? grouping.initial : []
2805
2853
  });
2806
- }
2807
- const [transientUiState, setTransientUiState] = useAtom(transientUiAtomRef.current);
2854
+ });
2855
+ const [transientUiState, setTransientUiState] = useAtom(transientUiAtom);
2808
2856
  const uiState = useMemo(() => ({ ...persistedUiState, ...transientUiState }), [persistedUiState, transientUiState]);
2809
2857
  const setUiState = useCallback((state) => {
2810
2858
  setPersistedUiState(persistedUiFromState(state));
2811
2859
  setTransientUiState(transientUiFromState(state));
2812
2860
  }, [setPersistedUiState, setTransientUiState]);
2813
2861
  const validatedConfig = resolveConfig(props, (state) => setUiState(state));
2814
- const atomRef = useRef(null);
2815
- if (!atomRef.current) {
2862
+ const [tableAtom] = useState(() => {
2816
2863
  const initial = {
2817
2864
  rowData: props.rowData,
2818
2865
  columnDefs: props.columnDefs,
@@ -2820,12 +2867,11 @@ var HydratedDataTable = ({
2820
2867
  state: initialPublicState(props, uiState),
2821
2868
  controlledState: props.state
2822
2869
  };
2823
- atomRef.current = Atom.make({
2870
+ return Atom.make({
2824
2871
  ...initial,
2825
2872
  model: buildDataTableModel(initial)
2826
2873
  });
2827
- }
2828
- const tableAtom = atomRef.current;
2874
+ });
2829
2875
  const setStore = useAtomSet(tableAtom);
2830
2876
  useLayoutEffect(() => {
2831
2877
  setStore((store) => {
@@ -2886,20 +2932,19 @@ var HydratedDataTable = ({
2886
2932
  };
2887
2933
  var DataTable = (props) => {
2888
2934
  const locale = useKrakstackLocale3(props.locale);
2889
- const persistedUiAtomRef = useRef(null);
2890
- if (!persistedUiAtomRef.current) {
2935
+ const [persistedUiAtom] = useState(() => {
2891
2936
  const pathname = globalThis.window?.location.pathname ?? "/";
2892
2937
  const columnIds = normalizeDataTableColumns(props.columnDefs).map(({ id }) => id).join(",");
2893
2938
  const viewCapability = props.features?.gallery ? "gallery" : "table";
2894
- persistedUiAtomRef.current = Atom.kvs({
2939
+ return Atom.kvs({
2895
2940
  mode: "async",
2896
2941
  runtime: dataTableStorageRuntime,
2897
2942
  key: `data-table:${pathname}:${columnIds}:${viewCapability}:ui`,
2898
2943
  schema: DataTablePersistedUiStateSchema,
2899
2944
  defaultValue: () => persistedUiFromState(DEFAULT_UI_STATE)
2900
2945
  });
2901
- }
2902
- const [persistedUiResult, setPersistedUiState] = useAtom(persistedUiAtomRef.current);
2946
+ });
2947
+ const [persistedUiResult, setPersistedUiState] = useAtom(persistedUiAtom);
2903
2948
  if (!AsyncResult.isSuccess(persistedUiResult) || AsyncResult.isWaiting(persistedUiResult)) {
2904
2949
  return /* @__PURE__ */ jsx14("div", {
2905
2950
  "aria-busy": "true",
@@ -3139,7 +3184,7 @@ var DataTableListMenu = ({
3139
3184
  event.stopPropagation();
3140
3185
  action.onClick(item);
3141
3186
  },
3142
- variant: action.variant,
3187
+ variant: action.variant ?? "default",
3143
3188
  children: [
3144
3189
  action.icon,
3145
3190
  action.name
@@ -22,6 +22,7 @@ export type GoogleMapMessages = {
22
22
  errorBody: string;
23
23
  zoomIn: string;
24
24
  zoomOut: string;
25
+ showArea: string;
25
26
  };
26
27
  export type GoogleMapMessageTranslations = Partial<Record<string, Partial<GoogleMapMessages>>>;
27
28
  export type GoogleMapProps = {
@@ -32,6 +33,8 @@ export type GoogleMapProps = {
32
33
  radius?: GoogleMapRadiusOptions | undefined;
33
34
  interactive?: boolean | undefined;
34
35
  zoomControls?: boolean | undefined;
36
+ fitRadius?: boolean | undefined;
37
+ fitPadding?: number | undefined;
35
38
  minZoom?: number | undefined;
36
39
  maxZoom?: number | undefined;
37
40
  className?: string | undefined;
@@ -55,11 +58,18 @@ type GoogleMapOptions = {
55
58
  zoomControl: boolean;
56
59
  };
57
60
  type GoogleMapInstance = {
61
+ fitBounds: (bounds: GoogleMapBounds, padding: number) => void;
58
62
  getZoom: () => number | undefined;
59
63
  setCenter: (center: GoogleMapPoint) => void;
60
64
  setOptions: (options: Partial<GoogleMapOptions>) => void;
61
65
  setZoom: (zoom: number) => void;
62
66
  };
67
+ type GoogleMapBounds = {
68
+ north: number;
69
+ south: number;
70
+ east: number;
71
+ west: number;
72
+ };
63
73
  type GoogleMarkerInstance = {
64
74
  setIcon: (icon?: string) => void;
65
75
  setMap: (map: GoogleMapInstance | null) => void;
@@ -67,6 +77,9 @@ type GoogleMarkerInstance = {
67
77
  };
68
78
  type GoogleCircleStyleOptions = Omit<GoogleMapRadiusOptions, "radiusMeters">;
69
79
  type GoogleCircleInstance = {
80
+ getBounds: () => {
81
+ toJSON: () => GoogleMapBounds;
82
+ } | undefined;
70
83
  setCenter: (center: GoogleMapPoint) => void;
71
84
  setMap: (map: GoogleMapInstance | null) => void;
72
85
  setOptions: (options: GoogleCircleStyleOptions) => void;
@@ -96,5 +109,5 @@ export type GoogleMapsApi = {
96
109
  };
97
110
  };
98
111
  };
99
- export declare function GoogleMap({ apiKey, center, zoom, marker, radius, interactive, zoomControls, minZoom, maxZoom, className, messages: messageOverrides, locale: localeOverride, }: GoogleMapProps): import("react").JSX.Element;
112
+ export declare function GoogleMap({ apiKey, center, zoom, marker, radius, interactive, zoomControls, fitRadius, fitPadding, minZoom, maxZoom, className, messages: messageOverrides, locale: localeOverride, }: GoogleMapProps): import("react").JSX.Element;
100
113
  export {};