@almadar/ui 6.57.0 → 6.58.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.
@@ -1,6 +1,6 @@
1
1
  import { useTranslate, useRenderI18n, runTickFrame } from './chunk-N7JIG57B.js';
2
2
  import { ALL_SLOTS } from './chunk-VHTJRS2N.js';
3
- import { LoadingState, ErrorState, UISlotRenderer } from './chunk-PYJ2UQWM.js';
3
+ import { LoadingState, ErrorState, UISlotRenderer } from './chunk-I2ZNE5TD.js';
4
4
  import { Box } from './chunk-HA7CJWQX.js';
5
5
  import { Typography } from './chunk-4ZGXIW3B.js';
6
6
  import { beginPending, endPending } from './chunk-I5QOYW2Z.js';
@@ -8,11 +8,11 @@ import { useEventBus } from './chunk-CHNC4UYE.js';
8
8
  import { registerTrait, unregisterTrait, perfTimeAsync } from './chunk-KJGVLXEN.js';
9
9
  import { bindTraitStateGetter, registerTraitSnapshot, recordTransition, recordingTransport, recordServerResponse } from './chunk-Q47ZN2DW.js';
10
10
  import { isNotificationSlot } from './chunk-PEST66PN.js';
11
- import React, { useState, useMemo, useEffect, useRef, useCallback, useSyncExternalStore } from 'react';
11
+ import React, { createContext, useState, useMemo, useEffect, useRef, useCallback, useSyncExternalStore, useContext } from 'react';
12
12
  import { schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, traitsEmbeddedByEvent, traitReferencesCallsitePayload, configReferencesCallsitePayload, TRAIT_MOUNT_ERROR_TESTID, traitsEmbeddedByEffects, applyListenPayloadMapping, buildResolvedTraitConfigs, collectCallsiteCaptureChildren, orbitalInlineEntities, storesRowsInBrowser, walkSExpr, isRenderBindingMarker, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER } from '@almadar/core';
13
13
  import { createLogger } from '@almadar/logger';
14
- import { collectListenerTargets, LIFECYCLE_EVENTS, TraitMountError, InMemoryPersistence, loadLazyPage, createInProcessTransport, buildTraitIndex, createMemoryCircuitStore, createLocalStoreTransport, createResidenceTransport, createClientKernel, createTickScheduler, isValidCronExpression, parseDurationString, UNMOUNT_EVENT, createClientEffectHandlers, EffectExecutor, isSExpression, collectDeclaredConfigDefaults, createContextFromBindings, openBrowserStore, browserStoreName, normalizeCallSiteConfigToValues } from '@almadar/runtime';
15
- import { useUser, useHasServerBridge, useArbitraryClassStyles, getAllPages, matchPathAmong, CurrentPagePathProvider, NavStackProvider, OrbitalProvider, ServerBridgeProvider, useNavStack, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge, EntityBindingContext, AwaitingSkeletonContext } from '@almadar/ui/providers';
14
+ import { collectListenerTargets, LIFECYCLE_EVENTS, TraitMountError, loadLazyPage, createInProcessTransport, buildTraitIndex, createMemoryCircuitStore, createLocalStoreTransport, createResidenceTransport, createClientKernel, createTickScheduler, isValidCronExpression, parseDurationString, UNMOUNT_EVENT, createClientEffectHandlers, EffectExecutor, isSExpression, collectDeclaredConfigDefaults, createContextFromBindings, openBrowserStore, browserStoreName, normalizeCallSiteConfigToValues } from '@almadar/runtime';
15
+ import { useUser, useArbitraryClassStyles, getAllPages, matchPathAmong, CurrentPagePathProvider, NavStackProvider, OrbitalProvider, ServerBridgeProvider, useNavStack, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge, EntityBindingContext, AwaitingSkeletonContext } from '@almadar/ui/providers';
16
16
  import { evaluateListenPayloadExpr, evaluate, createMinimalContext, evaluateGuard, executeEffects } from '@almadar/evaluator';
17
17
  import { getPatternDefinition } from '@almadar/core/patterns';
18
18
  import { isKnownStdOperator } from '@almadar/std/registry';
@@ -21,6 +21,7 @@ export { adjustSchemaForMockData, buildMockData, collectEmbeddedTraits, prepareS
21
21
  import { useInRouterContext, useHref } from 'react-router-dom';
22
22
  import { UISlotProvider, useUISlots } from '@almadar/ui/context';
23
23
  import { useTranslate as useTranslate$1, createTranslate, I18nProvider } from '@almadar/ui/hooks';
24
+ import { InMemoryPersistence } from '@almadar/db/mock';
24
25
  import { jsx, jsxs } from 'react/jsx-runtime';
25
26
  import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
26
27
  import { localeDirection } from '@almadar/core/i18n';
@@ -440,6 +441,22 @@ function useBusIngress(traitBindings, traitIndex, settle, eventBus, appTraitName
440
441
  });
441
442
  unsubscribes.push(unsub);
442
443
  }
444
+ const routedBareEvents = /* @__PURE__ */ new Set();
445
+ for (const binding of traitBindings) {
446
+ for (const listen of binding.trait.listens ?? []) {
447
+ if (listen.source !== void 0 && listen.source.kind !== "any") routedBareEvents.add(listen.event);
448
+ }
449
+ }
450
+ for (const eventKey of routedBareEvents) {
451
+ const unsub = eventBus.on(`UI:${eventKey}`, (event) => {
452
+ if (deliveredHere(event.source) || event.source?.trait === void 0) return;
453
+ for (const target of collectListenerTargets(traitIndex, event.source, eventKey, event.payload)) {
454
+ log2.debug("listen-bare:fire", { event: eventKey, source: event.source.trait, listener: target.listenerTrait, triggers: target.triggers });
455
+ dispatch(target.listenerTrait, target.triggers, target.payload, event.source);
456
+ }
457
+ });
458
+ unsubscribes.push(unsub);
459
+ }
443
460
  return () => {
444
461
  for (const unsub of unsubscribes) unsub();
445
462
  };
@@ -518,7 +535,7 @@ function resolveLambdaBindings(body, params, item, index) {
518
535
  return body;
519
536
  }
520
537
  var LazySlotContentRenderer = React.lazy(
521
- () => import('./UISlotRenderer-NCGDIXN4.js').then((m) => ({ default: m.SlotContentRenderer }))
538
+ () => import('./UISlotRenderer-N5DFYE6K.js').then((m) => ({ default: m.SlotContentRenderer }))
522
539
  );
523
540
  function deferLambdaEntityExprs(value) {
524
541
  if (typeof value === "string") {
@@ -1331,9 +1348,16 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1331
1348
  useEffect(() => {
1332
1349
  if (!transport?.subscribe) return;
1333
1350
  return transport.subscribe((emitted, target) => {
1334
- for (const t2 of collectListenerTargets(traitIndex, emitted.source, emitted.event, emitted.payload)) {
1335
- void (target === "origin" ? progressAndSettle(t2.listenerTrait, t2.triggers, t2.payload) : dispatchAndSettle(t2.listenerTrait, t2.triggers, t2.payload));
1351
+ const listeners = collectListenerTargets(traitIndex, emitted.source, emitted.event, emitted.payload);
1352
+ if (target !== "origin") {
1353
+ for (const t2 of listeners) void dispatchAndSettle(t2.listenerTrait, t2.triggers, t2.payload);
1354
+ return;
1355
+ }
1356
+ const caller = emitted.source?.trait;
1357
+ if (caller !== void 0 && traitIndex.byName.has(caller) && !listeners.some((t2) => t2.listenerTrait === caller && t2.triggers === emitted.event)) {
1358
+ void progressAndSettle(caller, emitted.event, emitted.payload);
1336
1359
  }
1360
+ for (const t2 of listeners) void progressAndSettle(t2.listenerTrait, t2.triggers, t2.payload);
1337
1361
  }, { clientId: getTabClientId() });
1338
1362
  }, [transport, traitIndex, progressAndSettle, dispatchAndSettle]);
1339
1363
  useEffect(() => {
@@ -1343,7 +1367,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1343
1367
  if (traitName === void 0 || !traitIndex.byName.has(traitName)) return;
1344
1368
  const composedBefore = new Map(store.callsitePayloads);
1345
1369
  void kernel.foldHostDispatch(request, response).then((outcome) => settleOutcome(traitName, request.event, request.payload, outcome, composedBefore));
1346
- });
1370
+ }, { clientId: getTabClientId() });
1347
1371
  }, [transport, traitIndex, kernel, store, settleOutcome]);
1348
1372
  const mountAndSettle = useCallback(async (seeds, payload) => {
1349
1373
  const composedBefore = new Map(store.callsitePayloads);
@@ -1404,6 +1428,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1404
1428
  prevActiveTraitNamesRef.current = nextActiveTraitNames;
1405
1429
  }, [traitBindings, uiSlots]);
1406
1430
  const mountedRef = useRef(null);
1431
+ const aliveRef = useRef(true);
1432
+ useEffect(() => {
1433
+ aliveRef.current = true;
1434
+ return () => {
1435
+ aliveRef.current = false;
1436
+ };
1437
+ }, []);
1407
1438
  const mountKey = `${options.mountKey ?? ""}\0${JSON.stringify(Object.entries(options.initPayload ?? {}).sort(([a], [b]) => a.localeCompare(b)))}`;
1408
1439
  useEffect(() => {
1409
1440
  const prev = mountedRef.current;
@@ -1434,6 +1465,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1434
1465
  const failed = err instanceof TraitMountError ? err.trait : traits.join(", ");
1435
1466
  const cause = err instanceof TraitMountError ? err.original : err;
1436
1467
  stateLog.warn("mount:failed", { traits, failed, error: message, stack: cause instanceof Error ? cause.stack : void 0 });
1468
+ if (!aliveRef.current) return;
1437
1469
  uiSlots.render({
1438
1470
  target: "main",
1439
1471
  pattern: "error-state",
@@ -1562,6 +1594,7 @@ function TraitInitializer({ traits, routeParams, mountKey, orbitals, onNavigate,
1562
1594
  }
1563
1595
  var CONTENT_STAGE_WIDTH = 480;
1564
1596
  var NO_ORBITALS = [];
1597
+ var LazyPageOfRegisteredProgram = createContext(false);
1565
1598
  function FitToBox({ children, mode = "box" }) {
1566
1599
  const outerRef = useRef(null);
1567
1600
  const innerRef = useRef(null);
@@ -1833,7 +1866,7 @@ function OrbPreview({
1833
1866
  if (localFallback) return;
1834
1867
  setLocalFallback(true);
1835
1868
  }, [localFallback]);
1836
- const hasOuterBridge = useHasServerBridge();
1869
+ const isLazyPageOfRegisteredProgram = useContext(LazyPageOfRegisteredProgram);
1837
1870
  const parseResult = useMemo(() => {
1838
1871
  let parsed;
1839
1872
  if (typeof schema === "string") {
@@ -2025,13 +2058,13 @@ function OrbPreview({
2025
2058
  navLog.info("interceptor:installed", { pageCount: pages.length, paths: pages.map((p) => p.page.path) });
2026
2059
  return () => el.removeEventListener("click", handler, true);
2027
2060
  }, [pages, handleNavigate, isolated, hostHrefBase]);
2028
- const withBridge = (node) => (serverUrl || transport) && !hasOuterBridge ? /* @__PURE__ */ jsx(ServerBridgeProvider, { schema: parseResult.schema, serverUrl, transport, getAccessToken, children: node }) : node;
2061
+ const withBridge = (node) => (serverUrl || transport) && !isLazyPageOfRegisteredProgram ? /* @__PURE__ */ jsx(ServerBridgeProvider, { schema: parseResult.schema, serverUrl, transport, getAccessToken, children: node }) : node;
2029
2062
  if (lazyRoute) {
2030
2063
  return withBridge(
2031
2064
  /* @__PURE__ */ jsxs(Box, { ref: containerRef, className: `overflow-auto ${className ?? ""}`, style: { height }, children: [
2032
2065
  lazyLoad.status === "loading" && /* @__PURE__ */ jsx(LoadingState, {}),
2033
2066
  lazyLoad.status === "error" && /* @__PURE__ */ jsx(ErrorState, { "data-testid": "lazy-page-error", message: lazyLoad.error }),
2034
- lazyLoad.status === "ready" && /* @__PURE__ */ jsx(
2067
+ lazyLoad.status === "ready" && /* @__PURE__ */ jsx(LazyPageOfRegisteredProgram.Provider, { value: true, children: /* @__PURE__ */ jsx(
2035
2068
  OrbPreview,
2036
2069
  {
2037
2070
  schema: lazyLoad.schema,
@@ -2051,7 +2084,7 @@ function OrbPreview({
2051
2084
  onUnmatchedNavigate: handleNavigate
2052
2085
  },
2053
2086
  lazyRoute.page.orbRef
2054
- )
2087
+ ) })
2055
2088
  ] })
2056
2089
  );
2057
2090
  }
@@ -2148,21 +2181,28 @@ function BrowserPlayground({
2148
2181
  runtime.resumeTicks();
2149
2182
  }
2150
2183
  }, [runtime, paused]);
2151
- const registrationReady = useMemo(() => {
2152
- const orbitalNames = schema.orbitals.map((o) => o.name);
2153
- playgroundLog.debug("register:start", { schema: schema.name, orbitalNames });
2184
+ const lazySchemas = useMemo(() => {
2154
2185
  const lazyPages = schema.lazyPages ?? [];
2155
- const lazy = lazyPages.length > 0 && lazyLoader !== void 0 ? Promise.all(lazyPages.map(async (page) => {
2186
+ if (lazyPages.length === 0 || lazyLoader === void 0) return Promise.resolve([]);
2187
+ return Promise.all(lazyPages.map(async (page) => {
2156
2188
  const loaded = await loadLazyPage(lazyLoader, page, schemaPath);
2157
2189
  if (!loaded.success) throw new Error(loaded.error);
2158
2190
  return loaded.data;
2159
- })) : Promise.resolve([]);
2160
- return lazy.then(
2161
- (lazySchemas) => runtime.register(schema, { ...messages !== void 0 ? { messages } : {}, lazy: lazySchemas })
2162
- ).then(() => {
2163
- playgroundLog.debug("register:done", { schema: schema.name, orbitalNames });
2191
+ }));
2192
+ }, [schema.lazyPages, lazyLoader, schemaPath]);
2193
+ const registerProgram = useCallback((program) => {
2194
+ const orbitalNames = program.orbitals.map((o) => o.name);
2195
+ playgroundLog.debug("register:start", { schema: program.name, orbitalNames });
2196
+ return lazySchemas.then((lazy) => runtime.register(program, { ...messages !== void 0 ? { messages } : {}, lazy })).then(() => {
2197
+ playgroundLog.debug("register:done", { schema: program.name, orbitalNames });
2164
2198
  });
2165
- }, [runtime, schema, messages, lazyLoader, schemaPath]);
2199
+ }, [runtime, lazySchemas, messages]);
2200
+ const latestRegistration = useRef(Promise.resolve());
2201
+ const registrationReady = useMemo(() => {
2202
+ const ready = registerProgram(schema);
2203
+ latestRegistration.current = ready;
2204
+ return ready;
2205
+ }, [registerProgram, schema]);
2166
2206
  const teardownTimerRef = useRef(null);
2167
2207
  useEffect(() => {
2168
2208
  if (teardownTimerRef.current !== null) {
@@ -2180,18 +2220,22 @@ function BrowserPlayground({
2180
2220
  const transport = useMemo(
2181
2221
  () => createInProcessTransport(
2182
2222
  async (orbitalName, request) => {
2183
- await registrationReady;
2223
+ await latestRegistration.current;
2184
2224
  return runtime.processOrbitalEvent(orbitalName, request);
2185
2225
  },
2186
2226
  {
2187
- // The program (lazy behaviors included) is registered above; the bridge only waits for it.
2188
- onRegister: () => registrationReady,
2227
+ // Registers what the bridge hands over (lazy behaviors included): a bridge that unregistered first gets it back.
2228
+ onRegister: (program) => {
2229
+ const ready = registerProgram(program);
2230
+ latestRegistration.current = ready;
2231
+ return ready;
2232
+ },
2189
2233
  onUnregister: () => {
2190
2234
  runtime.unregisterAll();
2191
2235
  }
2192
2236
  }
2193
2237
  ),
2194
- [runtime, registrationReady]
2238
+ [runtime, registerProgram, registrationReady]
2195
2239
  );
2196
2240
  const viewerLocale = locale ?? schema.locales?.[0];
2197
2241
  const i18n = useMemo(() => {
@@ -6850,7 +6850,7 @@ function renderVariant(variant, rows2, columns, fields, className) {
6850
6850
  }
6851
6851
  Skeleton.displayName = "Skeleton";
6852
6852
  var LazySlotContentRenderer = lazy(
6853
- () => import('./UISlotRenderer-NCGDIXN4.js').then((m) => ({
6853
+ () => import('./UISlotRenderer-N5DFYE6K.js').then((m) => ({
6854
6854
  default: m.SlotContentRenderer
6855
6855
  }))
6856
6856
  );
@@ -13098,6 +13098,7 @@ var Tabs = ({
13098
13098
  }
13099
13099
  };
13100
13100
  const activeTabContent = safeItems.find((item) => item.id === activeTab)?.content;
13101
+ const rendersPanel = activeTabContent !== void 0 && activeTabContent !== null;
13101
13102
  if (safeItems.length === 0) {
13102
13103
  return /* @__PURE__ */ jsx(Box, { ...domPassthrough(rest), className: cn("w-full", className), children: /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", className: "py-4", children: t("empty.noItems") }) });
13103
13104
  }
@@ -13148,7 +13149,7 @@ var Tabs = ({
13148
13149
  id: tabId(item.id),
13149
13150
  role: "tab",
13150
13151
  "aria-selected": isActive,
13151
- "aria-controls": isActive ? panelId(item.id) : void 0,
13152
+ "aria-controls": isActive && rendersPanel ? panelId(item.id) : void 0,
13152
13153
  "aria-disabled": isDisabled,
13153
13154
  tabIndex: isActive ? 0 : -1,
13154
13155
  onClick: () => !isDisabled && handleTabChange(item.id, item.event),
@@ -13208,7 +13209,7 @@ var Tabs = ({
13208
13209
  )
13209
13210
  ] })
13210
13211
  ] }) : tablist,
13211
- activeTabContent !== void 0 && activeTabContent !== null && /* @__PURE__ */ jsx(
13212
+ rendersPanel && /* @__PURE__ */ jsx(
13212
13213
  Box,
13213
13214
  {
13214
13215
  role: "tabpanel",
@@ -26909,8 +26910,11 @@ function DataGrid({
26909
26910
  children: [
26910
26911
  imageField && (() => {
26911
26912
  const imgUrl = resolveImageUrl(getNestedValue(itemData, imageField));
26912
- if (!imgUrl) return null;
26913
- return /* @__PURE__ */ jsx(Box, { className: asRows ? "w-12 h-12 flex-shrink-0 overflow-hidden rounded-container" : "w-full aspect-video overflow-hidden rounded-t-container", children: /* @__PURE__ */ jsx(
26913
+ const mediaClass = asRows ? "w-12 h-12 flex-shrink-0 overflow-hidden rounded-container" : "w-full aspect-video overflow-hidden rounded-t-container";
26914
+ if (!imgUrl) {
26915
+ return /* @__PURE__ */ jsx(Box, { "data-media-placeholder": true, "aria-hidden": "true", className: cn(mediaClass, "flex items-center justify-center bg-muted text-muted-foreground"), children: /* @__PURE__ */ jsx(Icon, { name: "image", size: asRows ? "sm" : "lg" }) });
26916
+ }
26917
+ return /* @__PURE__ */ jsx(Box, { className: mediaClass, children: /* @__PURE__ */ jsx(
26914
26918
  "img",
26915
26919
  {
26916
26920
  src: imgUrl,
@@ -26943,7 +26947,7 @@ function DataGrid({
26943
26947
  Typography,
26944
26948
  {
26945
26949
  variant: titleField?.variant === "h3" && !asRows ? "h3" : "h4",
26946
- className: "font-semibold truncate min-w-0",
26950
+ className: cn("font-semibold min-w-0", asRows ? "truncate" : "line-clamp-2 break-words"),
26947
26951
  children: titleDisplay
26948
26952
  }
26949
26953
  )
@@ -27027,6 +27031,19 @@ function DataGrid({
27027
27031
  ));
27028
27032
  }
27029
27033
  DataGrid.displayName = "DataGrid";
27034
+
27035
+ // lib/groupRows.ts
27036
+ function groupRows(items, field, format, fmt2 = {}) {
27037
+ const groups = /* @__PURE__ */ new Map();
27038
+ for (const item of items) {
27039
+ const raw = getNestedValue(item, field);
27040
+ const key = format ? formatValue(raw ?? void 0, format, fmt2) : String(raw ?? "");
27041
+ const group = groups.get(key);
27042
+ if (group) group.push(item);
27043
+ else groups.set(key, [item]);
27044
+ }
27045
+ return Array.from(groups.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
27046
+ }
27030
27047
  var dataListLog = createLogger("almadar:ui:data-list");
27031
27048
  function renderIconInput2(icon, props) {
27032
27049
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
@@ -27037,16 +27054,6 @@ function formatValue2(value, format, fmt2, relationOptions) {
27037
27054
  if (relationDisplay !== void 0) return relationDisplay;
27038
27055
  return formatValue(value, format, fmt2);
27039
27056
  }
27040
- function groupData(items, field) {
27041
- const groups = /* @__PURE__ */ new Map();
27042
- for (const item of items) {
27043
- const key = String(getNestedValue(item, field) ?? "");
27044
- const group = groups.get(key);
27045
- if (group) group.push(item);
27046
- else groups.set(key, [item]);
27047
- }
27048
- return Array.from(groups.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
27049
- }
27050
27057
  var listLookStyles = {
27051
27058
  dense: "[&_[data-entity-row]>div]:!py-1 [&_[data-entity-row]>div]:!px-3",
27052
27059
  spacious: "[&_[data-entity-row]>div]:!py-5 [&_[data-entity-row]>div]:!px-8",
@@ -27064,6 +27071,7 @@ function DataList({
27064
27071
  gap = "none",
27065
27072
  variant = "default",
27066
27073
  groupBy,
27074
+ groupFormat,
27067
27075
  senderField,
27068
27076
  senderLabelField,
27069
27077
  currentUser,
@@ -27171,9 +27179,9 @@ function DataList({
27171
27179
  e.stopPropagation();
27172
27180
  fireRowAction(action, itemData, String(itemData.id ?? ""));
27173
27181
  };
27174
- const renderItemActions = (itemData, onPrimary = false) => {
27182
+ const renderItemActions = (itemData, onPrimary = false, only) => {
27175
27183
  if (!itemActions || itemActions.length === 0) return null;
27176
- const shown = rowActions(itemActions, itemData);
27184
+ const shown = only ?? rowActions(itemActions, itemData);
27177
27185
  if (shown.length === 0) return null;
27178
27186
  const inline = maxInlineActions != null ? shown.slice(0, maxInlineActions) : shown;
27179
27187
  const overflow = maxInlineActions != null ? shown.slice(maxInlineActions) : [];
@@ -27257,7 +27265,7 @@ function DataList({
27257
27265
  const isMessage = variant === "message";
27258
27266
  if (isMessage) {
27259
27267
  const items2 = [...data];
27260
- const groups2 = groupBy ? groupData(items2, groupBy) : [{ label: "", items: items2 }];
27268
+ const groups2 = groupBy ? groupRows(items2, groupBy, groupFormat, fmt2) : [{ label: "", items: items2 }];
27261
27269
  const contentField = (fieldDefs.find((f3) => f3.variant === "body") ?? titleField)?.name ?? "";
27262
27270
  const senderLabel = (itemData, raw) => {
27263
27271
  if (!senderLabelField) return raw;
@@ -27354,7 +27362,7 @@ function DataList({
27354
27362
  ] });
27355
27363
  }
27356
27364
  const items = [...data];
27357
- const groups = groupBy ? groupData(items, groupBy) : [{ label: "", items }];
27365
+ const groups = groupBy ? groupRows(items, groupBy, groupFormat, fmt2) : [{ label: "", items }];
27358
27366
  const idFieldName = dndItemIdField ?? "id";
27359
27367
  const renderItem = (itemData, index, isLast) => {
27360
27368
  const dndId = itemData[idFieldName] ?? `__idx_${index}`;
@@ -27365,7 +27373,11 @@ function DataList({
27365
27373
  const divider = (isCard || isCompact) && !isLast ? /* @__PURE__ */ jsx(Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") }) : null;
27366
27374
  if (hasRenderProp) {
27367
27375
  const id2 = itemData.id || String(index);
27368
- const actions = renderItemActions(itemData);
27376
+ const allActions = itemActions ? rowActions(itemActions, itemData) : [];
27377
+ const primaryActions = allActions.filter((a) => a.variant === "primary");
27378
+ const otherActions = allActions.filter((a) => a.variant !== "primary");
27379
+ const primary = primaryActions.length > 0 ? renderItemActions(itemData, false, primaryActions) : null;
27380
+ const actions = otherActions.length > 0 ? renderItemActions(itemData, false, otherActions) : null;
27369
27381
  return wrapDnd(
27370
27382
  /* @__PURE__ */ jsxs(
27371
27383
  Box,
@@ -27386,6 +27398,7 @@ function DataList({
27386
27398
  }
27387
27399
  ),
27388
27400
  /* @__PURE__ */ jsx(Box, { className: cn(onOpen && "relative"), children: itemRenderer(itemData, index) }),
27401
+ primary && /* @__PURE__ */ jsx(HStack, { justify: "end", className: cn("relative px-4 pb-3", STRETCHED_ABOVE), children: primary }),
27389
27402
  actions && /* @__PURE__ */ jsxs(Box, { onClick: (e) => e.stopPropagation(), className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
27390
27403
  /* @__PURE__ */ jsx(Box, { className: "rounded-container border border-border bg-card/95 backdrop-blur-sm shadow-elevation-popover surface-material p-0.5 [@media(pointer:coarse)]:hidden", children: actions }),
27391
27404
  /* @__PURE__ */ jsx(Box, { className: "hidden [@media(pointer:coarse)]:block rounded-container border border-border bg-card/95 backdrop-blur-sm shadow-elevation-popover surface-material p-0.5", children: /* @__PURE__ */ jsx(
@@ -27393,7 +27406,7 @@ function DataList({
27393
27406
  {
27394
27407
  position: "bottom-end",
27395
27408
  trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
27396
- items: rowActions(itemActions ?? [], itemData).map((action) => ({
27409
+ items: otherActions.map((action) => ({
27397
27410
  label: action.label,
27398
27411
  icon: action.icon,
27399
27412
  variant: action.variant === "danger" ? "danger" : "default",
@@ -28564,16 +28577,6 @@ var formatCell = (value, format, relationOptions, fmt2) => {
28564
28577
  if (relationDisplay !== void 0) return relationDisplay;
28565
28578
  return formatValue(value, format, fmt2);
28566
28579
  };
28567
- function groupData2(items, field) {
28568
- const groups = /* @__PURE__ */ new Map();
28569
- for (const item of items) {
28570
- const key = String(getNestedValue(item, field) ?? "");
28571
- const group = groups.get(key);
28572
- if (group) group.push(item);
28573
- else groups.set(key, [item]);
28574
- }
28575
- return Array.from(groups.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
28576
- }
28577
28580
  var MAX_MEASURED_COL_CH = 32;
28578
28581
  var BADGE_CHROME_CH = 4;
28579
28582
  var alignClass = {
@@ -28903,7 +28906,7 @@ function TableView({
28903
28906
  return dnd.isZone ? /* @__PURE__ */ jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsx(React93__default.Fragment, { children: rowInner }, id);
28904
28907
  };
28905
28908
  const items = Array.from(data);
28906
- const groups = groupBy ? groupData2(items, groupBy) : [{ label: "", items }];
28909
+ const groups = groupBy ? groupRows(items, groupBy) : [{ label: "", items }];
28907
28910
  let runningIndex = 0;
28908
28911
  const body = /* @__PURE__ */ jsx(Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxs(React93__default.Fragment, { children: [
28909
28912
  group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
@@ -10934,6 +10934,7 @@ var init_Tabs = __esm({
10934
10934
  }
10935
10935
  };
10936
10936
  const activeTabContent = safeItems.find((item) => item.id === activeTab)?.content;
10937
+ const rendersPanel = activeTabContent !== void 0 && activeTabContent !== null;
10937
10938
  if (safeItems.length === 0) {
10938
10939
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { ...domPassthrough(rest), className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "muted", className: "py-4", children: t("empty.noItems") }) });
10939
10940
  }
@@ -10984,7 +10985,7 @@ var init_Tabs = __esm({
10984
10985
  id: tabId(item.id),
10985
10986
  role: "tab",
10986
10987
  "aria-selected": isActive,
10987
- "aria-controls": isActive ? panelId(item.id) : void 0,
10988
+ "aria-controls": isActive && rendersPanel ? panelId(item.id) : void 0,
10988
10989
  "aria-disabled": isDisabled,
10989
10990
  tabIndex: isActive ? 0 : -1,
10990
10991
  onClick: () => !isDisabled && handleTabChange(item.id, item.event),
@@ -11044,7 +11045,7 @@ var init_Tabs = __esm({
11044
11045
  )
11045
11046
  ] })
11046
11047
  ] }) : tablist,
11047
- activeTabContent !== void 0 && activeTabContent !== null && /* @__PURE__ */ jsxRuntime.jsx(
11048
+ rendersPanel && /* @__PURE__ */ jsxRuntime.jsx(
11048
11049
  exports.Box,
11049
11050
  {
11050
11051
  role: "tabpanel",
@@ -32212,8 +32213,11 @@ function DataGrid({
32212
32213
  children: [
32213
32214
  imageField && (() => {
32214
32215
  const imgUrl = resolveImageUrl(core.getNestedValue(itemData, imageField));
32215
- if (!imgUrl) return null;
32216
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: asRows ? "w-12 h-12 flex-shrink-0 overflow-hidden rounded-container" : "w-full aspect-video overflow-hidden rounded-t-container", children: /* @__PURE__ */ jsxRuntime.jsx(
32216
+ const mediaClass = asRows ? "w-12 h-12 flex-shrink-0 overflow-hidden rounded-container" : "w-full aspect-video overflow-hidden rounded-t-container";
32217
+ if (!imgUrl) {
32218
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { "data-media-placeholder": true, "aria-hidden": "true", className: cn(mediaClass, "flex items-center justify-center bg-muted text-muted-foreground"), children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "image", size: asRows ? "sm" : "lg" }) });
32219
+ }
32220
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: mediaClass, children: /* @__PURE__ */ jsxRuntime.jsx(
32217
32221
  "img",
32218
32222
  {
32219
32223
  src: imgUrl,
@@ -32246,7 +32250,7 @@ function DataGrid({
32246
32250
  exports.Typography,
32247
32251
  {
32248
32252
  variant: titleField?.variant === "h3" && !asRows ? "h3" : "h4",
32249
- className: "font-semibold truncate min-w-0",
32253
+ className: cn("font-semibold min-w-0", asRows ? "truncate" : "line-clamp-2 break-words"),
32250
32254
  children: titleDisplay
32251
32255
  }
32252
32256
  )
@@ -32375,24 +32379,33 @@ var init_DataGrid = __esm({
32375
32379
  DataGrid.displayName = "DataGrid";
32376
32380
  }
32377
32381
  });
32378
- function renderIconInput2(icon, props) {
32379
- return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon, ...props });
32380
- }
32381
- function formatValue2(value, format, fmt2, relationOptions) {
32382
- const relationDisplay = resolveRelationCellDisplay(value, relationOptions);
32383
- if (relationDisplay !== void 0) return relationDisplay;
32384
- return formatValue(value, format, fmt2);
32385
- }
32386
- function groupData(items, field) {
32382
+
32383
+ // lib/groupRows.ts
32384
+ function groupRows(items, field, format, fmt2 = {}) {
32387
32385
  const groups = /* @__PURE__ */ new Map();
32388
32386
  for (const item of items) {
32389
- const key = String(core.getNestedValue(item, field) ?? "");
32387
+ const raw = core.getNestedValue(item, field);
32388
+ const key = format ? formatValue(raw ?? void 0, format, fmt2) : String(raw ?? "");
32390
32389
  const group = groups.get(key);
32391
32390
  if (group) group.push(item);
32392
32391
  else groups.set(key, [item]);
32393
32392
  }
32394
32393
  return Array.from(groups.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
32395
32394
  }
32395
+ var init_groupRows = __esm({
32396
+ "lib/groupRows.ts"() {
32397
+ init_format();
32398
+ init_getNestedValue();
32399
+ }
32400
+ });
32401
+ function renderIconInput2(icon, props) {
32402
+ return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon, ...props });
32403
+ }
32404
+ function formatValue2(value, format, fmt2, relationOptions) {
32405
+ const relationDisplay = resolveRelationCellDisplay(value, relationOptions);
32406
+ if (relationDisplay !== void 0) return relationDisplay;
32407
+ return formatValue(value, format, fmt2);
32408
+ }
32396
32409
  function DataList({
32397
32410
  entity,
32398
32411
  fields,
@@ -32403,6 +32416,7 @@ function DataList({
32403
32416
  gap = "none",
32404
32417
  variant = "default",
32405
32418
  groupBy,
32419
+ groupFormat,
32406
32420
  senderField,
32407
32421
  senderLabelField,
32408
32422
  currentUser,
@@ -32510,9 +32524,9 @@ function DataList({
32510
32524
  e.stopPropagation();
32511
32525
  fireRowAction(action, itemData, String(itemData.id ?? ""));
32512
32526
  };
32513
- const renderItemActions = (itemData, onPrimary = false) => {
32527
+ const renderItemActions = (itemData, onPrimary = false, only) => {
32514
32528
  if (!itemActions || itemActions.length === 0) return null;
32515
- const shown = rowActions(itemActions, itemData);
32529
+ const shown = only ?? rowActions(itemActions, itemData);
32516
32530
  if (shown.length === 0) return null;
32517
32531
  const inline = maxInlineActions != null ? shown.slice(0, maxInlineActions) : shown;
32518
32532
  const overflow = maxInlineActions != null ? shown.slice(maxInlineActions) : [];
@@ -32596,7 +32610,7 @@ function DataList({
32596
32610
  const isMessage = variant === "message";
32597
32611
  if (isMessage) {
32598
32612
  const items2 = [...data];
32599
- const groups2 = groupBy ? groupData(items2, groupBy) : [{ label: "", items: items2 }];
32613
+ const groups2 = groupBy ? groupRows(items2, groupBy, groupFormat, fmt2) : [{ label: "", items: items2 }];
32600
32614
  const contentField = (fieldDefs.find((f3) => f3.variant === "body") ?? titleField)?.name ?? "";
32601
32615
  const senderLabel = (itemData, raw) => {
32602
32616
  if (!senderLabelField) return raw;
@@ -32693,7 +32707,7 @@ function DataList({
32693
32707
  ] });
32694
32708
  }
32695
32709
  const items = [...data];
32696
- const groups = groupBy ? groupData(items, groupBy) : [{ label: "", items }];
32710
+ const groups = groupBy ? groupRows(items, groupBy, groupFormat, fmt2) : [{ label: "", items }];
32697
32711
  const idFieldName = dndItemIdField ?? "id";
32698
32712
  const renderItem = (itemData, index, isLast) => {
32699
32713
  const dndId = itemData[idFieldName] ?? `__idx_${index}`;
@@ -32704,7 +32718,11 @@ function DataList({
32704
32718
  const divider = (isCard || isCompact) && !isLast ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") }) : null;
32705
32719
  if (hasRenderProp) {
32706
32720
  const id2 = itemData.id || String(index);
32707
- const actions = renderItemActions(itemData);
32721
+ const allActions = itemActions ? rowActions(itemActions, itemData) : [];
32722
+ const primaryActions = allActions.filter((a) => a.variant === "primary");
32723
+ const otherActions = allActions.filter((a) => a.variant !== "primary");
32724
+ const primary = primaryActions.length > 0 ? renderItemActions(itemData, false, primaryActions) : null;
32725
+ const actions = otherActions.length > 0 ? renderItemActions(itemData, false, otherActions) : null;
32708
32726
  return wrapDnd(
32709
32727
  /* @__PURE__ */ jsxRuntime.jsxs(
32710
32728
  exports.Box,
@@ -32725,6 +32743,7 @@ function DataList({
32725
32743
  }
32726
32744
  ),
32727
32745
  /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: cn(onOpen && "relative"), children: itemRenderer(itemData, index) }),
32746
+ primary && /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { justify: "end", className: cn("relative px-4 pb-3", STRETCHED_ABOVE), children: primary }),
32728
32747
  actions && /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { onClick: (e) => e.stopPropagation(), className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
32729
32748
  /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "rounded-container border border-border bg-card/95 backdrop-blur-sm shadow-elevation-popover surface-material p-0.5 [@media(pointer:coarse)]:hidden", children: actions }),
32730
32749
  /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "hidden [@media(pointer:coarse)]:block rounded-container border border-border bg-card/95 backdrop-blur-sm shadow-elevation-popover surface-material p-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -32732,7 +32751,7 @@ function DataList({
32732
32751
  {
32733
32752
  position: "bottom-end",
32734
32753
  trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
32735
- items: rowActions(itemActions ?? [], itemData).map((action) => ({
32754
+ items: otherActions.map((action) => ({
32736
32755
  label: action.label,
32737
32756
  icon: action.icon,
32738
32757
  variant: action.variant === "danger" ? "danger" : "default",
@@ -32923,6 +32942,7 @@ var init_DataList = __esm({
32923
32942
  init_format();
32924
32943
  init_relationLabel();
32925
32944
  init_getNestedValue();
32945
+ init_groupRows();
32926
32946
  init_useEventBus();
32927
32947
  init_useRowActions();
32928
32948
  init_Box();
@@ -34016,16 +34036,6 @@ function asFieldValue(v) {
34016
34036
  if (t === "object") return v;
34017
34037
  return void 0;
34018
34038
  }
34019
- function groupData2(items, field) {
34020
- const groups = /* @__PURE__ */ new Map();
34021
- for (const item of items) {
34022
- const key = String(core.getNestedValue(item, field) ?? "");
34023
- const group = groups.get(key);
34024
- if (group) group.push(item);
34025
- else groups.set(key, [item]);
34026
- }
34027
- return Array.from(groups.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
34028
- }
34029
34039
  function TableView({
34030
34040
  entity,
34031
34041
  columns,
@@ -34336,7 +34346,7 @@ function TableView({
34336
34346
  return dnd.isZone ? /* @__PURE__ */ jsxRuntime.jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsxRuntime.jsx(React89__namespace.default.Fragment, { children: rowInner }, id);
34337
34347
  };
34338
34348
  const items = Array.from(data);
34339
- const groups = groupBy ? groupData2(items, groupBy) : [{ label: "", items }];
34349
+ const groups = groupBy ? groupRows(items, groupBy) : [{ label: "", items }];
34340
34350
  let runningIndex = 0;
34341
34351
  const body = /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React89__namespace.default.Fragment, { children: [
34342
34352
  group.label && /* @__PURE__ */ jsxRuntime.jsx(exports.Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
@@ -34375,6 +34385,7 @@ var init_TableView = __esm({
34375
34385
  init_format();
34376
34386
  init_relationLabel();
34377
34387
  init_getNestedValue();
34388
+ init_groupRows();
34378
34389
  init_useEventBus();
34379
34390
  init_useRowActions();
34380
34391
  init_Box();