@efa-one/sdk 1.24.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -269,6 +269,18 @@ Excel export for such data. No backend or kernel change.
269
269
  - After the bump every dialog without `size` is 768 px wide. Give short confirmations
270
270
  ("Wirklich löschen?") `size="sm"`.
271
271
 
272
+ **1.25.0 — dismissible banners, top scrollbar.** No kernel or backend change.
273
+ - Every `Alert` shows a close „×" top right by default. With `onDismiss` nothing
274
+ changes. Without it the banner hides itself and shows again when its text changes.
275
+ `dismissible={false}` removes the „×" for notices that must stay.
276
+ - Messages from app state (errors, results) need `onDismiss`: without it, a closed
277
+ banner stays hidden when the app sets the same text again in one handler or when
278
+ the content is JSX. After the bump, add `onDismiss` (and `onErrorDismiss` on
279
+ `RecordDialog`) wherever such banners lack it.
280
+ - `DataTable`: when the grid is wider than its container, a second horizontal
281
+ scrollbar sits above the table and scrolls in sync with the bottom one. Not on
282
+ mobile. The border moved to an outer wrapper.
283
+
272
284
  **Next minor (planned 1.23.0, not yet released) — exchange, cookie and S2S hardening.**
273
285
  Needs a kernel with efaware/efa_kernel#106 and #107 first (gateway exchange token
274
286
  `gw: 'app'`, provenance `jti`, cookie filter). Then:
@@ -4,7 +4,10 @@ interface AlertProps {
4
4
  variant?: AlertVariant;
5
5
  children: React.ReactNode;
6
6
  onDismiss?: () => void;
7
+ dismissible?: boolean;
7
8
  className?: string;
8
9
  }
9
- export declare function Alert({ variant, children, onDismiss, className }: AlertProps): React.JSX.Element;
10
+ export declare function alertContentKey(children: React.ReactNode): string | null;
11
+ export declare function isAlertHidden(dismissedKey: string | null | undefined, contentKey: string | null): boolean;
12
+ export declare function Alert({ variant, children, onDismiss, dismissible, className }: AlertProps): React.JSX.Element | null;
10
13
  export {};
@@ -1,5 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
2
3
  import { X } from 'lucide-react';
3
- export function Alert({ variant = 'error', children, onDismiss, className = '' }) {
4
- return (_jsxs("div", { role: "alert", className: `shrink-0 flex items-start gap-2 border rounded-md px-4 py-3 text-sm font-medium alert-${variant} ${className}`, children: [_jsx("span", { className: "flex-1", children: children }), onDismiss && (_jsx("button", { type: "button", onClick: onDismiss, "aria-label": "Meldung schlie\u00DFen", className: "shrink-0 -mr-1 p-0.5 rounded hover:bg-current/10 transition-colors", children: _jsx(X, { className: "w-4 h-4" }) }))] }));
4
+ export function alertContentKey(children) {
5
+ return typeof children === 'string' || typeof children === 'number' ? String(children) : null;
6
+ }
7
+ export function isAlertHidden(dismissedKey, contentKey) {
8
+ return dismissedKey !== undefined && dismissedKey === contentKey;
9
+ }
10
+ export function Alert({ variant = 'error', children, onDismiss, dismissible = true, className = '' }) {
11
+ const [dismissedKey, setDismissedKey] = useState(undefined);
12
+ const contentKey = alertContentKey(children);
13
+ if (!onDismiss && isAlertHidden(dismissedKey, contentKey))
14
+ return null;
15
+ return (_jsxs("div", { role: "alert", className: `shrink-0 flex items-start gap-2 border rounded-md px-4 py-3 text-sm font-medium alert-${variant} ${className}`, children: [_jsx("span", { className: "flex-1 min-w-0", children: children }), dismissible && (_jsx("button", { type: "button", onClick: onDismiss ?? (() => setDismissedKey(contentKey)), "aria-label": "Meldung schlie\u00DFen", className: "shrink-0 -mr-1 p-0.5 rounded hover:bg-current/10 transition-colors", children: _jsx(X, { className: "w-4 h-4" }) }))] }));
5
16
  }
@@ -89,4 +89,5 @@ interface RenderItem<T> {
89
89
  export declare function buildRenderItems<T>(rows: T[], groupBy: string[], columnsById: Map<string, ColumnDef<T>>, expandedGroups: Set<string> | 'all'): RenderItem<T>[];
90
90
  export declare function collectAllGroupKeys<T>(rows: T[], groupBy: string[], columnsById: Map<string, ColumnDef<T>>): string[];
91
91
  export declare function DataTable<T, K extends string | number>({ listId, rows, columns, rowKey, onRowClick, selection, initialPrefs, footer, groupsDefaultExpanded, persistence, exportable, exportFileName, }: DataTableProps<T, K>): React.ReactElement;
92
+ export declare function topScrollbarWidth(gridWidth: number, viewportWidth: number): number | null;
92
93
  export {};
@@ -241,6 +241,7 @@ export function DataTable({ listId, rows, columns, rowKey, onRowClick, selection
241
241
  const [dragColumnId, setDragColumnId] = useState(null);
242
242
  const [dropTarget, setDropTarget] = useState(null);
243
243
  const containerRef = useRef(null);
244
+ const headerRef = useRef(null);
244
245
  const effectiveOrder = useMemo(() => reconcileOrder(columns, prefs.columnOrder), [columns, prefs.columnOrder]);
245
246
  const seenGroupKeysRef = useRef(new Set());
246
247
  useEffect(() => {
@@ -433,42 +434,85 @@ export function DataTable({ listId, rows, columns, rowKey, onRowClick, selection
433
434
  hasColumns: visibleColumns.some((c) => c.exportable !== false),
434
435
  run: runExport,
435
436
  } : undefined, columns: columns, prefs: prefs, setColumnVisible: setColumnVisible, moveColumn: moveColumn, reset: reset, groupBy: groupBy, addGroupBy: addGroupBy, removeGroupBy: removeGroupBy, moveGroupBy: moveGroupBy, clearGroupBy: clearGroupBy }));
436
- return (_jsxs("div", { ref: containerRef, className: "border border-[var(--color-border)] overflow-x-auto", style: { '--dt-cols': gridTemplate }, children: [selection && selection.selected.size > 0 && (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-sm border-b border-[var(--color-border)]", style: { background: 'var(--color-surface-raised)' }, children: [_jsxs("span", { children: [selection.selected.size, " ausgew\u00E4hlt"] }), _jsx("button", { type: "button", className: "text-xs underline text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]", onClick: () => selection.onChange(new Set()), children: "Auswahl aufheben" }), _jsx("div", { className: "flex-1" }), selection.bulkActions] })), isMobile ? (_jsxs("div", { className: "flex items-center gap-3 px-3 py-2 text-xs font-medium border-b border-[var(--color-border)]", style: { background: 'var(--color-surface-raised)' }, children: [selection && (_jsxs("label", { className: "flex items-center gap-2 py-2 pr-2 cursor-pointer", children: [_jsx("input", { type: "checkbox", className: "w-5 h-5", checked: allSelected, onChange: toggleAll, "aria-label": "Alle ausw\u00E4hlen" }), _jsx("span", { className: "text-[var(--color-text-muted)]", children: "Alle" })] })), _jsx("div", { className: "flex-1" }), gearButton] })) : (_jsxs("div", { className: "grid items-center min-w-min px-3 py-2 text-xs font-medium border-b border-[var(--color-border)]", style: { gridTemplateColumns: 'var(--dt-cols)', background: 'var(--color-surface-raised)' }, children: [selection && (_jsx("input", { type: "checkbox", checked: allSelected, onChange: toggleAll, "aria-label": "Alle ausw\u00E4hlen" })), visibleColumns.map((col) => {
437
- const activeSort = prefs.sort?.columnId === col.id ? prefs.sort.direction : null;
438
- const filterValue = prefs.filters[col.id];
439
- const isFiltered = filterValue != null;
440
- return (_jsx(HeaderCell, { columnId: col.id, label: col.label, dragColumnId: dragColumnId, dropSide: dropTarget?.id === col.id ? dropTarget.side : null, onDragStart: () => setDragColumnId(col.id), onDragEnd: () => { setDragColumnId(null); setDropTarget(null); }, onDragOverSide: (side) => {
441
- if (dropTarget?.id !== col.id || dropTarget.side !== side)
442
- setDropTarget({ id: col.id, side });
443
- }, onDrop: (sourceId, side) => {
444
- dropColumn(sourceId, col.id, side);
445
- setDragColumnId(null);
446
- setDropTarget(null);
447
- }, onResizePreview: (px) => previewColumnWidth(col.id, px), onResizeCommit: (px) => setColumnWidth(col.id, px), onResizeCancel: () => containerRef.current?.style.setProperty('--dt-cols', gridTemplate), hasCustomWidth: columnWidths[col.id] != null, children: _jsxs(DropdownMenu.Root, { children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex items-center gap-1 min-w-0 px-1 py-0.5 hover:bg-[var(--color-surface)] text-left", style: { borderRadius: 'var(--border-radius-md)' }, children: [_jsx("span", { className: "min-w-0 truncate", children: col.label }), activeSort === 'asc' && _jsx(ChevronUp, { className: "w-3 h-3" }), activeSort === 'desc' && _jsx(ChevronDown, { className: "w-3 h-3" }), isFiltered && _jsx(Filter, { className: "w-3 h-3 signal-text-primary" })] }) }), _jsxs(DropdownMenu.Content, { className: "min-w-[220px] p-1", children: [col.sortable !== false && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenu.Item, { onSelect: () => setSort(col.id, 'asc'), children: [_jsx(ArrowUp, { className: "w-4 h-4" }), "Sortieren A\u2192Z"] }), _jsxs(DropdownMenu.Item, { onSelect: () => setSort(col.id, 'desc'), children: [_jsx(ArrowDown, { className: "w-4 h-4" }), "Sortieren Z\u2192A"] }), activeSort && (_jsx(DropdownMenu.Item, { onSelect: clearSort, children: "Sortierung zur\u00FCcksetzen" })), _jsx(DropdownMenu.Separator, {})] })), groupBy.includes(col.id) ? (_jsxs(DropdownMenu.Item, { onSelect: () => removeGroupBy(col.id), children: [_jsx(Layers, { className: "w-4 h-4" }), "Gruppierung aufheben"] })) : (_jsxs(DropdownMenu.Item, { onSelect: () => addGroupBy(col.id), children: [_jsx(Layers, { className: "w-4 h-4" }), "Nach dieser Spalte gruppieren"] })), _jsx(DropdownMenu.Separator, {}), col.filter && (_jsx(FilterEditor, { column: col, value: filterValue, onChange: (v) => setFilter(col.id, v) })), columnWidths[col.id] != null && (_jsxs(DropdownMenu.Item, { onSelect: () => setColumnWidth(col.id, null), children: [_jsx(RotateCcw, { className: "w-4 h-4" }), "Standardbreite"] })), _jsxs(DropdownMenu.Item, { onSelect: () => setColumnVisible(col.id, false), variant: "danger", children: [_jsx(EyeOff, { className: "w-4 h-4" }), "Spalte ausblenden"] })] })] }) }, col.id));
448
- }), gearButton] })), sortedFilteredRows.length === 0 ? (_jsx("div", { className: "px-3 py-8 text-center text-sm text-[var(--color-text-muted)]", children: "Keine Eintr\u00E4ge" })) : (buildRenderItems(sortedFilteredRows, groupBy, columnsById, expandedGroups).map((item, idx) => {
449
- if (item.kind === 'group') {
450
- const level = item.level ?? 0;
451
- const groupColLabel = columnsById.get(groupBy[level])?.label ?? '';
452
- return (_jsx("div", { className: "px-3 py-1.5 text-xs border-b border-[var(--color-border)] cursor-pointer select-none hover:bg-[var(--color-surface-raised)]", style: {
453
- background: 'var(--color-surface-raised)',
454
- paddingLeft: 12 + level * 18,
455
- }, onClick: () => toggleGroup(item.key), title: item.expanded ? 'Gruppe einklappen' : 'Gruppe ausklappen', children: _jsxs("div", { className: "flex items-center gap-2", children: [item.expanded
456
- ? _jsx(ChevronDown, { className: "w-3.5 h-3.5 text-[var(--color-text-muted)]" })
457
- : _jsx(ChevronRight, { className: "w-3.5 h-3.5 text-[var(--color-text-muted)]" }), _jsx("span", { className: "text-[var(--color-text-muted)] uppercase text-[10px]", children: groupColLabel }), _jsx("span", { className: "font-medium text-[var(--color-text-primary)]", children: item.label }), _jsxs("span", { className: "text-[var(--color-text-muted)]", children: ["(", item.count, ")"] })] }) }, `g:${item.key}`));
458
- }
459
- const row = item.row;
460
- const key = rowKey(row);
461
- const isChecked = selection?.selected.has(key) ?? false;
462
- const rowIndent = groupBy.length > 0 ? 12 + groupBy.length * 18 : undefined;
463
- if (isMobile) {
464
- return (_jsxs("div", { className: `flex flex-col gap-1.5 px-3 py-3 border-b border-[var(--color-border)] ${onRowClick ? 'cursor-pointer hover:bg-[var(--color-surface-raised)]' : ''}`, style: { background: isChecked ? 'rgba(99,102,241,0.07)' : undefined, paddingLeft: rowIndent }, onClick: () => onRowClick?.(row), children: [selection && (_jsxs("label", { className: "flex items-center gap-2 self-start py-1 pr-2 cursor-pointer", onClick: (e) => e.stopPropagation(), children: [_jsx("input", { type: "checkbox", className: "w-5 h-5", checked: isChecked, onChange: () => toggleOne(key), "aria-label": "Zeile ausw\u00E4hlen" }), _jsx("span", { className: "text-xs text-[var(--color-text-muted)]", children: "Ausw\u00E4hlen" })] })), visibleColumns.map((col) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: col.label }), _jsx("div", { className: "min-w-0 break-words text-sm", children: col.cell ? col.cell(row) : formatCellValue(col, row) })] }, col.id)))] }, `r:${String(key)}:${idx}`));
465
- }
466
- return (_jsxs("div", { className: `grid items-center min-w-min px-3 py-2 text-sm border-b border-[var(--color-border)] ${onRowClick ? 'cursor-pointer hover:bg-[var(--color-surface-raised)]' : ''}`, style: {
467
- gridTemplateColumns: 'var(--dt-cols)',
468
- background: isChecked ? 'rgba(99,102,241,0.07)' : undefined,
469
- paddingLeft: rowIndent,
470
- }, onClick: () => onRowClick?.(row), children: [selection && (_jsx("input", { type: "checkbox", checked: isChecked, onChange: () => toggleOne(key), onClick: (e) => e.stopPropagation(), "aria-label": "Zeile ausw\u00E4hlen" })), visibleColumns.map((col) => (_jsx("div", { className: "min-w-0 truncate", children: col.cell ? col.cell(row) : formatCellValue(col, row) }, col.id))), _jsx("div", {})] }, `r:${String(key)}:${idx}`));
471
- })), footer && (_jsx("div", { className: "px-3 py-2 text-xs text-[var(--color-text-muted)] border-t border-[var(--color-border)]", children: footer }))] }));
437
+ return (_jsxs("div", { className: "border border-[var(--color-border)]", children: [_jsx(TopScrollbar, { targetRef: containerRef, measureRef: headerRef, enabled: !isMobile }), _jsxs("div", { ref: containerRef, className: "overflow-x-auto", style: { '--dt-cols': gridTemplate }, children: [selection && selection.selected.size > 0 && (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-sm border-b border-[var(--color-border)]", style: { background: 'var(--color-surface-raised)' }, children: [_jsxs("span", { children: [selection.selected.size, " ausgew\u00E4hlt"] }), _jsx("button", { type: "button", className: "text-xs underline text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]", onClick: () => selection.onChange(new Set()), children: "Auswahl aufheben" }), _jsx("div", { className: "flex-1" }), selection.bulkActions] })), isMobile ? (_jsxs("div", { className: "flex items-center gap-3 px-3 py-2 text-xs font-medium border-b border-[var(--color-border)]", style: { background: 'var(--color-surface-raised)' }, children: [selection && (_jsxs("label", { className: "flex items-center gap-2 py-2 pr-2 cursor-pointer", children: [_jsx("input", { type: "checkbox", className: "w-5 h-5", checked: allSelected, onChange: toggleAll, "aria-label": "Alle ausw\u00E4hlen" }), _jsx("span", { className: "text-[var(--color-text-muted)]", children: "Alle" })] })), _jsx("div", { className: "flex-1" }), gearButton] })) : (_jsxs("div", { ref: headerRef, className: "grid items-center min-w-min px-3 py-2 text-xs font-medium border-b border-[var(--color-border)]", style: { gridTemplateColumns: 'var(--dt-cols)', background: 'var(--color-surface-raised)' }, children: [selection && (_jsx("input", { type: "checkbox", checked: allSelected, onChange: toggleAll, "aria-label": "Alle ausw\u00E4hlen" })), visibleColumns.map((col) => {
438
+ const activeSort = prefs.sort?.columnId === col.id ? prefs.sort.direction : null;
439
+ const filterValue = prefs.filters[col.id];
440
+ const isFiltered = filterValue != null;
441
+ return (_jsx(HeaderCell, { columnId: col.id, label: col.label, dragColumnId: dragColumnId, dropSide: dropTarget?.id === col.id ? dropTarget.side : null, onDragStart: () => setDragColumnId(col.id), onDragEnd: () => { setDragColumnId(null); setDropTarget(null); }, onDragOverSide: (side) => {
442
+ if (dropTarget?.id !== col.id || dropTarget.side !== side)
443
+ setDropTarget({ id: col.id, side });
444
+ }, onDrop: (sourceId, side) => {
445
+ dropColumn(sourceId, col.id, side);
446
+ setDragColumnId(null);
447
+ setDropTarget(null);
448
+ }, onResizePreview: (px) => previewColumnWidth(col.id, px), onResizeCommit: (px) => setColumnWidth(col.id, px), onResizeCancel: () => containerRef.current?.style.setProperty('--dt-cols', gridTemplate), hasCustomWidth: columnWidths[col.id] != null, children: _jsxs(DropdownMenu.Root, { children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex items-center gap-1 min-w-0 px-1 py-0.5 hover:bg-[var(--color-surface)] text-left", style: { borderRadius: 'var(--border-radius-md)' }, children: [_jsx("span", { className: "min-w-0 truncate", children: col.label }), activeSort === 'asc' && _jsx(ChevronUp, { className: "w-3 h-3" }), activeSort === 'desc' && _jsx(ChevronDown, { className: "w-3 h-3" }), isFiltered && _jsx(Filter, { className: "w-3 h-3 signal-text-primary" })] }) }), _jsxs(DropdownMenu.Content, { className: "min-w-[220px] p-1", children: [col.sortable !== false && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenu.Item, { onSelect: () => setSort(col.id, 'asc'), children: [_jsx(ArrowUp, { className: "w-4 h-4" }), "Sortieren A\u2192Z"] }), _jsxs(DropdownMenu.Item, { onSelect: () => setSort(col.id, 'desc'), children: [_jsx(ArrowDown, { className: "w-4 h-4" }), "Sortieren Z\u2192A"] }), activeSort && (_jsx(DropdownMenu.Item, { onSelect: clearSort, children: "Sortierung zur\u00FCcksetzen" })), _jsx(DropdownMenu.Separator, {})] })), groupBy.includes(col.id) ? (_jsxs(DropdownMenu.Item, { onSelect: () => removeGroupBy(col.id), children: [_jsx(Layers, { className: "w-4 h-4" }), "Gruppierung aufheben"] })) : (_jsxs(DropdownMenu.Item, { onSelect: () => addGroupBy(col.id), children: [_jsx(Layers, { className: "w-4 h-4" }), "Nach dieser Spalte gruppieren"] })), _jsx(DropdownMenu.Separator, {}), col.filter && (_jsx(FilterEditor, { column: col, value: filterValue, onChange: (v) => setFilter(col.id, v) })), columnWidths[col.id] != null && (_jsxs(DropdownMenu.Item, { onSelect: () => setColumnWidth(col.id, null), children: [_jsx(RotateCcw, { className: "w-4 h-4" }), "Standardbreite"] })), _jsxs(DropdownMenu.Item, { onSelect: () => setColumnVisible(col.id, false), variant: "danger", children: [_jsx(EyeOff, { className: "w-4 h-4" }), "Spalte ausblenden"] })] })] }) }, col.id));
449
+ }), gearButton] })), sortedFilteredRows.length === 0 ? (_jsx("div", { className: "px-3 py-8 text-center text-sm text-[var(--color-text-muted)]", children: "Keine Eintr\u00E4ge" })) : (buildRenderItems(sortedFilteredRows, groupBy, columnsById, expandedGroups).map((item, idx) => {
450
+ if (item.kind === 'group') {
451
+ const level = item.level ?? 0;
452
+ const groupColLabel = columnsById.get(groupBy[level])?.label ?? '';
453
+ return (_jsx("div", { className: "px-3 py-1.5 text-xs border-b border-[var(--color-border)] cursor-pointer select-none hover:bg-[var(--color-surface-raised)]", style: {
454
+ background: 'var(--color-surface-raised)',
455
+ paddingLeft: 12 + level * 18,
456
+ }, onClick: () => toggleGroup(item.key), title: item.expanded ? 'Gruppe einklappen' : 'Gruppe ausklappen', children: _jsxs("div", { className: "flex items-center gap-2", children: [item.expanded
457
+ ? _jsx(ChevronDown, { className: "w-3.5 h-3.5 text-[var(--color-text-muted)]" })
458
+ : _jsx(ChevronRight, { className: "w-3.5 h-3.5 text-[var(--color-text-muted)]" }), _jsx("span", { className: "text-[var(--color-text-muted)] uppercase text-[10px]", children: groupColLabel }), _jsx("span", { className: "font-medium text-[var(--color-text-primary)]", children: item.label }), _jsxs("span", { className: "text-[var(--color-text-muted)]", children: ["(", item.count, ")"] })] }) }, `g:${item.key}`));
459
+ }
460
+ const row = item.row;
461
+ const key = rowKey(row);
462
+ const isChecked = selection?.selected.has(key) ?? false;
463
+ const rowIndent = groupBy.length > 0 ? 12 + groupBy.length * 18 : undefined;
464
+ if (isMobile) {
465
+ return (_jsxs("div", { className: `flex flex-col gap-1.5 px-3 py-3 border-b border-[var(--color-border)] ${onRowClick ? 'cursor-pointer hover:bg-[var(--color-surface-raised)]' : ''}`, style: { background: isChecked ? 'rgba(99,102,241,0.07)' : undefined, paddingLeft: rowIndent }, onClick: () => onRowClick?.(row), children: [selection && (_jsxs("label", { className: "flex items-center gap-2 self-start py-1 pr-2 cursor-pointer", onClick: (e) => e.stopPropagation(), children: [_jsx("input", { type: "checkbox", className: "w-5 h-5", checked: isChecked, onChange: () => toggleOne(key), "aria-label": "Zeile ausw\u00E4hlen" }), _jsx("span", { className: "text-xs text-[var(--color-text-muted)]", children: "Ausw\u00E4hlen" })] })), visibleColumns.map((col) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: col.label }), _jsx("div", { className: "min-w-0 break-words text-sm", children: col.cell ? col.cell(row) : formatCellValue(col, row) })] }, col.id)))] }, `r:${String(key)}:${idx}`));
466
+ }
467
+ return (_jsxs("div", { className: `grid items-center min-w-min px-3 py-2 text-sm border-b border-[var(--color-border)] ${onRowClick ? 'cursor-pointer hover:bg-[var(--color-surface-raised)]' : ''}`, style: {
468
+ gridTemplateColumns: 'var(--dt-cols)',
469
+ background: isChecked ? 'rgba(99,102,241,0.07)' : undefined,
470
+ paddingLeft: rowIndent,
471
+ }, onClick: () => onRowClick?.(row), children: [selection && (_jsx("input", { type: "checkbox", checked: isChecked, onChange: () => toggleOne(key), onClick: (e) => e.stopPropagation(), "aria-label": "Zeile ausw\u00E4hlen" })), visibleColumns.map((col) => (_jsx("div", { className: "min-w-0 truncate", children: col.cell ? col.cell(row) : formatCellValue(col, row) }, col.id))), _jsx("div", {})] }, `r:${String(key)}:${idx}`));
472
+ })), footer && (_jsx("div", { className: "px-3 py-2 text-xs text-[var(--color-text-muted)] border-t border-[var(--color-border)]", children: footer }))] })] }));
473
+ }
474
+ export function topScrollbarWidth(gridWidth, viewportWidth) {
475
+ return gridWidth > viewportWidth + 1 ? gridWidth : null;
476
+ }
477
+ function syncScroll(from, to) {
478
+ if (Math.abs(to.scrollLeft - from.scrollLeft) > 1)
479
+ to.scrollLeft = from.scrollLeft;
480
+ }
481
+ function TopScrollbar({ targetRef, measureRef, enabled }) {
482
+ const barRef = useRef(null);
483
+ const [width, setWidth] = useState(null);
484
+ useEffect(() => {
485
+ const target = targetRef.current;
486
+ const measured = measureRef.current;
487
+ if (!enabled || !target || !measured || typeof ResizeObserver === 'undefined') {
488
+ setWidth(null);
489
+ return;
490
+ }
491
+ const update = () => setWidth(topScrollbarWidth(target.scrollWidth, target.clientWidth));
492
+ const observer = new ResizeObserver(update);
493
+ observer.observe(target);
494
+ observer.observe(measured);
495
+ update();
496
+ const follow = () => {
497
+ if (barRef.current)
498
+ syncScroll(target, barRef.current);
499
+ };
500
+ target.addEventListener('scroll', follow, { passive: true });
501
+ return () => {
502
+ observer.disconnect();
503
+ target.removeEventListener('scroll', follow);
504
+ };
505
+ }, [enabled, targetRef, measureRef]);
506
+ useEffect(() => {
507
+ if (barRef.current && targetRef.current)
508
+ barRef.current.scrollLeft = targetRef.current.scrollLeft;
509
+ }, [width, targetRef]);
510
+ if (width == null)
511
+ return null;
512
+ return (_jsx("div", { ref: barRef, "aria-hidden": "true", tabIndex: -1, style: { overflowX: 'auto', overflowY: 'hidden', minHeight: 12 }, onScroll: () => {
513
+ if (targetRef.current && barRef.current)
514
+ syncScroll(barRef.current, targetRef.current);
515
+ }, children: _jsx("div", { style: { width, height: 1 } }) }));
472
516
  }
473
517
  const DRAG_MIME = 'application/x-efa-datatable-column';
474
518
  function HeaderCell({ columnId, label, dragColumnId, dropSide, hasCustomWidth, onDragStart, onDragEnd, onDragOverSide, onDrop, onResizePreview, onResizeCommit, onResizeCancel, children, }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@efa-one/sdk",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "Offizielles SDK für efa-one-Apps: Auth/Session, Service-Discovery, Gateway-Client, Audit/Reporting/Mail/Notifications, Permission- und Capability-Registrierung sowie das Frontend-IPC-/i18n-Toolkit.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "commonjs",