@urbicon-ui/table 6.1.4
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 +153 -0
- package/dist/cells/ActionButtons.svelte +224 -0
- package/dist/cells/ActionButtons.svelte.d.ts +74 -0
- package/dist/cells/CopyButton.svelte +89 -0
- package/dist/cells/CopyButton.svelte.d.ts +33 -0
- package/dist/cells/CustomCell.svelte +136 -0
- package/dist/cells/CustomCell.svelte.d.ts +44 -0
- package/dist/cells/DateCell.svelte +194 -0
- package/dist/cells/DateCell.svelte.d.ts +39 -0
- package/dist/cells/LinkCell.svelte +240 -0
- package/dist/cells/LinkCell.svelte.d.ts +42 -0
- package/dist/cells/NumberCell.svelte +225 -0
- package/dist/cells/NumberCell.svelte.d.ts +47 -0
- package/dist/cells/StatusBadge.svelte +121 -0
- package/dist/cells/StatusBadge.svelte.d.ts +44 -0
- package/dist/cells/UserAvatar.svelte +71 -0
- package/dist/cells/UserAvatar.svelte.d.ts +37 -0
- package/dist/cells/index.d.ts +8 -0
- package/dist/cells/index.js +9 -0
- package/dist/core/EmptyState.svelte +161 -0
- package/dist/core/EmptyState.svelte.d.ts +16 -0
- package/dist/core/ErrorState.svelte +158 -0
- package/dist/core/ErrorState.svelte.d.ts +15 -0
- package/dist/core/GroupedRow.svelte +239 -0
- package/dist/core/GroupedRow.svelte.d.ts +18 -0
- package/dist/core/LoadingState.svelte +75 -0
- package/dist/core/LoadingState.svelte.d.ts +14 -0
- package/dist/core/MobileCard.svelte +151 -0
- package/dist/core/MobileCard.svelte.d.ts +15 -0
- package/dist/core/TableCell.svelte +105 -0
- package/dist/core/TableCell.svelte.d.ts +14 -0
- package/dist/core/TableDesktop.svelte +480 -0
- package/dist/core/TableDesktop.svelte.d.ts +26 -0
- package/dist/core/TableHead.svelte +314 -0
- package/dist/core/TableHead.svelte.d.ts +7 -0
- package/dist/core/TableMobile.svelte +112 -0
- package/dist/core/TableMobile.svelte.d.ts +13 -0
- package/dist/core/TableProvider.svelte +271 -0
- package/dist/core/TableProvider.svelte.d.ts +40 -0
- package/dist/core/TableRow.svelte +171 -0
- package/dist/core/TableRow.svelte.d.ts +16 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +14 -0
- package/dist/core/sticky-context.svelte.d.ts +48 -0
- package/dist/core/sticky-context.svelte.js +88 -0
- package/dist/core/table/Table.svelte +304 -0
- package/dist/core/table/Table.svelte.d.ts +26 -0
- package/dist/core/table/index.d.ts +448 -0
- package/dist/core/table/index.js +1 -0
- package/dist/core/table-style-context.d.ts +66 -0
- package/dist/core/table-style-context.js +26 -0
- package/dist/factories/ColumnValidation.d.ts +49 -0
- package/dist/factories/ColumnValidation.js +188 -0
- package/dist/factories/TableColumns.d.ts +97 -0
- package/dist/factories/TableColumns.js +262 -0
- package/dist/factories/TypedColumnBuilder.d.ts +41 -0
- package/dist/factories/TypedColumnBuilder.js +72 -0
- package/dist/factories/index.d.ts +12 -0
- package/dist/factories/index.js +13 -0
- package/dist/features/HeaderMenu.svelte +236 -0
- package/dist/features/HeaderMenu.svelte.d.ts +8 -0
- package/dist/features/LiveUpdateBanner.svelte +66 -0
- package/dist/features/LiveUpdateBanner.svelte.d.ts +6 -0
- package/dist/features/SearchHighlight.svelte +21 -0
- package/dist/features/SearchHighlight.svelte.d.ts +8 -0
- package/dist/features/SmartFilterBar/ChipsField.svelte +104 -0
- package/dist/features/SmartFilterBar/ChipsField.svelte.d.ts +5 -0
- package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte +84 -0
- package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/FilterMenu.svelte +367 -0
- package/dist/features/SmartFilterBar/FilterMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/GroupingMenu.svelte +82 -0
- package/dist/features/SmartFilterBar/GroupingMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/SmartFilterBar.svelte +109 -0
- package/dist/features/SmartFilterBar/SmartFilterBar.svelte.d.ts +11 -0
- package/dist/features/SmartFilterBar/SummaryMenu.svelte +118 -0
- package/dist/features/SmartFilterBar/SummaryMenu.svelte.d.ts +3 -0
- package/dist/features/SummaryRow.svelte +97 -0
- package/dist/features/SummaryRow.svelte.d.ts +8 -0
- package/dist/features/index.d.ts +4 -0
- package/dist/features/index.js +4 -0
- package/dist/i18n/index.d.ts +366 -0
- package/dist/i18n/index.js +21 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +41 -0
- package/dist/stores/TableStore.svelte.d.ts +192 -0
- package/dist/stores/TableStore.svelte.js +362 -0
- package/dist/stores/concerns/index.d.ts +15 -0
- package/dist/stores/concerns/index.js +14 -0
- package/dist/stores/concerns/types.d.ts +31 -0
- package/dist/stores/concerns/types.js +1 -0
- package/dist/stores/concerns/useColumnOrder.svelte.d.ts +16 -0
- package/dist/stores/concerns/useColumnOrder.svelte.js +81 -0
- package/dist/stores/concerns/useColumnVisibility.svelte.d.ts +16 -0
- package/dist/stores/concerns/useColumnVisibility.svelte.js +58 -0
- package/dist/stores/concerns/useExpansion.svelte.d.ts +9 -0
- package/dist/stores/concerns/useExpansion.svelte.js +32 -0
- package/dist/stores/concerns/useFiltering.svelte.d.ts +20 -0
- package/dist/stores/concerns/useFiltering.svelte.js +109 -0
- package/dist/stores/concerns/useFocusManagement.svelte.d.ts +15 -0
- package/dist/stores/concerns/useFocusManagement.svelte.js +52 -0
- package/dist/stores/concerns/useGrouping.svelte.d.ts +15 -0
- package/dist/stores/concerns/useGrouping.svelte.js +86 -0
- package/dist/stores/concerns/useLiveUpdates.svelte.d.ts +45 -0
- package/dist/stores/concerns/useLiveUpdates.svelte.js +175 -0
- package/dist/stores/concerns/usePagination.svelte.d.ts +18 -0
- package/dist/stores/concerns/usePagination.svelte.js +54 -0
- package/dist/stores/concerns/usePersistence.svelte.d.ts +36 -0
- package/dist/stores/concerns/usePersistence.svelte.js +167 -0
- package/dist/stores/concerns/useRemoteData.svelte.d.ts +21 -0
- package/dist/stores/concerns/useRemoteData.svelte.js +64 -0
- package/dist/stores/concerns/useSearch.svelte.d.ts +8 -0
- package/dist/stores/concerns/useSearch.svelte.js +16 -0
- package/dist/stores/concerns/useSelection.svelte.d.ts +21 -0
- package/dist/stores/concerns/useSelection.svelte.js +110 -0
- package/dist/stores/concerns/useSorting.svelte.d.ts +11 -0
- package/dist/stores/concerns/useSorting.svelte.js +70 -0
- package/dist/stores/concerns/useSummary.svelte.d.ts +18 -0
- package/dist/stores/concerns/useSummary.svelte.js +96 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.js +1 -0
- package/dist/style/index.css +137 -0
- package/dist/style/index.d.ts +2 -0
- package/dist/style/index.js +2 -0
- package/dist/style/table-theme.css +131 -0
- package/dist/style/themes/comfortable.css +20 -0
- package/dist/style/themes/compact.css +20 -0
- package/dist/translations/de.d.ts +177 -0
- package/dist/translations/de.js +176 -0
- package/dist/translations/en.d.ts +177 -0
- package/dist/translations/en.js +176 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/tableTypes.d.ts +262 -0
- package/dist/types/tableTypes.js +1 -0
- package/dist/utils/index.d.ts +165 -0
- package/dist/utils/index.js +330 -0
- package/dist/utils/sticky-measure.d.ts +54 -0
- package/dist/utils/sticky-measure.js +107 -0
- package/dist/utils/virtualizer.d.ts +43 -0
- package/dist/utils/virtualizer.js +43 -0
- package/dist/variants/index.d.ts +11 -0
- package/dist/variants/index.js +15 -0
- package/dist/variants/table-cells.variants.d.ts +827 -0
- package/dist/variants/table-cells.variants.js +627 -0
- package/dist/variants/table-features.variants.d.ts +547 -0
- package/dist/variants/table-features.variants.js +412 -0
- package/dist/variants/table-states.variants.d.ts +594 -0
- package/dist/variants/table-states.variants.js +394 -0
- package/dist/variants/table.system.d.ts +301 -0
- package/dist/variants/table.system.js +314 -0
- package/dist/variants/table.variants.d.ts +428 -0
- package/dist/variants/table.variants.js +360 -0
- package/package.json +93 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<script lang="ts" generics="Item">
|
|
2
|
+
import type { Component, Snippet } from 'svelte';
|
|
3
|
+
import { useTableI18n } from '../i18n';
|
|
4
|
+
import { customCellVariants, type CustomCellVariantProps } from '../variants';
|
|
5
|
+
|
|
6
|
+
const tt = useTableI18n();
|
|
7
|
+
|
|
8
|
+
export type CustomCellProps<Item> = {
|
|
9
|
+
item: Item;
|
|
10
|
+
content?: (item: Item) => string;
|
|
11
|
+
component?: Component<Record<string, unknown>>;
|
|
12
|
+
componentProps?: Record<string, unknown>;
|
|
13
|
+
children?: Snippet<[item: Item]>;
|
|
14
|
+
class?: string;
|
|
15
|
+
style?: string;
|
|
16
|
+
align?: CustomCellVariantProps['align'];
|
|
17
|
+
wrap?: CustomCellVariantProps['wrap'];
|
|
18
|
+
truncate?: CustomCellVariantProps['truncate'];
|
|
19
|
+
interactive?: CustomCellVariantProps['interactive'];
|
|
20
|
+
size?: CustomCellVariantProps['size'];
|
|
21
|
+
onClick?: (item: Item) => void;
|
|
22
|
+
onDoubleClick?: (item: Item) => void;
|
|
23
|
+
title?: string | ((item: Item) => string);
|
|
24
|
+
testId?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
item,
|
|
29
|
+
content = undefined,
|
|
30
|
+
component = undefined,
|
|
31
|
+
componentProps = {},
|
|
32
|
+
class: className = '',
|
|
33
|
+
style = '',
|
|
34
|
+
align = 'left',
|
|
35
|
+
wrap = false,
|
|
36
|
+
truncate = true,
|
|
37
|
+
interactive = false,
|
|
38
|
+
size = 'md',
|
|
39
|
+
onClick = undefined,
|
|
40
|
+
onDoubleClick = undefined,
|
|
41
|
+
title = undefined,
|
|
42
|
+
testId = undefined,
|
|
43
|
+
children = undefined
|
|
44
|
+
}: CustomCellProps<Item> = $props();
|
|
45
|
+
|
|
46
|
+
// Compute dynamic properties
|
|
47
|
+
const isClickable = $derived(Boolean(onClick || onDoubleClick || interactive));
|
|
48
|
+
|
|
49
|
+
const computedTitle = $derived.by(() => {
|
|
50
|
+
if (typeof title === 'function') {
|
|
51
|
+
return title(item);
|
|
52
|
+
}
|
|
53
|
+
return title;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const computedTestId = $derived.by(() => {
|
|
57
|
+
if (testId) return testId;
|
|
58
|
+
if (item && typeof item === 'object' && 'id' in item) {
|
|
59
|
+
return `custom-cell-${item.id}`;
|
|
60
|
+
}
|
|
61
|
+
return 'custom-cell';
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Tailwind-Variants styling
|
|
65
|
+
const styles = $derived(
|
|
66
|
+
customCellVariants({
|
|
67
|
+
align,
|
|
68
|
+
wrap,
|
|
69
|
+
truncate,
|
|
70
|
+
interactive: isClickable,
|
|
71
|
+
size
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Event handlers
|
|
76
|
+
function handleClick(event: MouseEvent) {
|
|
77
|
+
if (onClick) {
|
|
78
|
+
event.stopPropagation();
|
|
79
|
+
onClick(item);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handleDoubleClick(event: MouseEvent) {
|
|
84
|
+
if (onDoubleClick) {
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
onDoubleClick(item);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
91
|
+
if (isClickable && (event.key === 'Enter' || event.key === ' ')) {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
if (onClick) {
|
|
94
|
+
onClick(item);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
101
|
+
<div
|
|
102
|
+
class="{styles.container()} {className}"
|
|
103
|
+
{style}
|
|
104
|
+
title={computedTitle}
|
|
105
|
+
data-testid={computedTestId}
|
|
106
|
+
onclick={handleClick}
|
|
107
|
+
ondblclick={handleDoubleClick}
|
|
108
|
+
onkeydown={handleKeyDown}
|
|
109
|
+
role={isClickable ? 'button' : undefined}
|
|
110
|
+
tabindex={isClickable ? 0 : undefined}
|
|
111
|
+
aria-label={isClickable ? tt('aria.interactiveCell') : undefined}
|
|
112
|
+
>
|
|
113
|
+
<div class={styles.content()}>
|
|
114
|
+
{#if component}
|
|
115
|
+
<!-- Svelte Component Rendering -->
|
|
116
|
+
{@const Component = component}
|
|
117
|
+
<Component {item} {...componentProps} />
|
|
118
|
+
{:else if content}
|
|
119
|
+
<!-- HTML Content Function — consumers opt into this API explicitly
|
|
120
|
+
and are responsible for sanitising the output; the `content`
|
|
121
|
+
callback is their authoring surface. -->
|
|
122
|
+
<div class={styles.text()}>
|
|
123
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
124
|
+
{@html content(item)}
|
|
125
|
+
</div>
|
|
126
|
+
{:else if children}
|
|
127
|
+
<!-- Snippet-based Content -->
|
|
128
|
+
{@render children(item)}
|
|
129
|
+
{:else}
|
|
130
|
+
<!-- Default fallback content -->
|
|
131
|
+
<span class={styles.fallback()}>
|
|
132
|
+
{JSON.stringify(item)}
|
|
133
|
+
</span>
|
|
134
|
+
{/if}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Component, Snippet } from 'svelte';
|
|
2
|
+
import { type CustomCellVariantProps } from '../variants';
|
|
3
|
+
export type CustomCellProps<Item> = {
|
|
4
|
+
item: Item;
|
|
5
|
+
content?: (item: Item) => string;
|
|
6
|
+
component?: Component<Record<string, unknown>>;
|
|
7
|
+
componentProps?: Record<string, unknown>;
|
|
8
|
+
children?: Snippet<[item: Item]>;
|
|
9
|
+
class?: string;
|
|
10
|
+
style?: string;
|
|
11
|
+
align?: CustomCellVariantProps['align'];
|
|
12
|
+
wrap?: CustomCellVariantProps['wrap'];
|
|
13
|
+
truncate?: CustomCellVariantProps['truncate'];
|
|
14
|
+
interactive?: CustomCellVariantProps['interactive'];
|
|
15
|
+
size?: CustomCellVariantProps['size'];
|
|
16
|
+
onClick?: (item: Item) => void;
|
|
17
|
+
onDoubleClick?: (item: Item) => void;
|
|
18
|
+
title?: string | ((item: Item) => string);
|
|
19
|
+
testId?: string;
|
|
20
|
+
};
|
|
21
|
+
declare function $$render<Item>(): {
|
|
22
|
+
props: CustomCellProps<Item>;
|
|
23
|
+
exports: {};
|
|
24
|
+
bindings: "";
|
|
25
|
+
slots: {};
|
|
26
|
+
events: {};
|
|
27
|
+
};
|
|
28
|
+
declare class __sveltets_Render<Item> {
|
|
29
|
+
props(): ReturnType<typeof $$render<Item>>['props'];
|
|
30
|
+
events(): ReturnType<typeof $$render<Item>>['events'];
|
|
31
|
+
slots(): ReturnType<typeof $$render<Item>>['slots'];
|
|
32
|
+
bindings(): "";
|
|
33
|
+
exports(): {};
|
|
34
|
+
}
|
|
35
|
+
interface $$IsomorphicComponent {
|
|
36
|
+
new <Item>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Item>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Item>['props']>, ReturnType<__sveltets_Render<Item>['events']>, ReturnType<__sveltets_Render<Item>['slots']>> & {
|
|
37
|
+
$$bindings?: ReturnType<__sveltets_Render<Item>['bindings']>;
|
|
38
|
+
} & ReturnType<__sveltets_Render<Item>['exports']>;
|
|
39
|
+
<Item>(internal: unknown, props: ReturnType<__sveltets_Render<Item>['props']> & {}): ReturnType<__sveltets_Render<Item>['exports']>;
|
|
40
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
41
|
+
}
|
|
42
|
+
declare const CustomCell: $$IsomorphicComponent;
|
|
43
|
+
type CustomCell<Item> = InstanceType<typeof CustomCell<Item>>;
|
|
44
|
+
export default CustomCell;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<script lang="ts" generics="Item">
|
|
2
|
+
import { dateCellVariants, type DateCellVariantProps } from '../variants';
|
|
3
|
+
|
|
4
|
+
export type DateCellProps<Item> = {
|
|
5
|
+
item: Item;
|
|
6
|
+
dateKey?: keyof Item;
|
|
7
|
+
format?: 'short' | 'medium' | 'long' | 'relative' | 'datetime';
|
|
8
|
+
customFormat?: (date: Date) => string;
|
|
9
|
+
locale?: string;
|
|
10
|
+
showTime?: boolean;
|
|
11
|
+
timezone?: string;
|
|
12
|
+
fallback?: string;
|
|
13
|
+
onClick?: (item: Item, date: Date | null) => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
testId?: string;
|
|
16
|
+
size?: DateCellVariantProps['size'];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
item,
|
|
21
|
+
dateKey = undefined,
|
|
22
|
+
format = 'medium',
|
|
23
|
+
customFormat = undefined,
|
|
24
|
+
locale = undefined as string | undefined,
|
|
25
|
+
showTime = false,
|
|
26
|
+
timezone = undefined,
|
|
27
|
+
fallback = '—',
|
|
28
|
+
onClick = undefined,
|
|
29
|
+
className = '',
|
|
30
|
+
testId = undefined,
|
|
31
|
+
size = 'md'
|
|
32
|
+
}: DateCellProps<Item> = $props();
|
|
33
|
+
|
|
34
|
+
// Extract date value from item
|
|
35
|
+
const extractDate = (item: Item, key?: keyof Item): Date | null => {
|
|
36
|
+
if (!key) return null;
|
|
37
|
+
|
|
38
|
+
const value = item[key];
|
|
39
|
+
if (!value) return null;
|
|
40
|
+
|
|
41
|
+
if (value instanceof Date) return value;
|
|
42
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
43
|
+
const parsed = new Date(value);
|
|
44
|
+
return isNaN(parsed.getTime()) ? null : parsed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Get the date value
|
|
51
|
+
const dateValue = $derived.by(() => extractDate(item, dateKey));
|
|
52
|
+
|
|
53
|
+
// Check if date is clickable
|
|
54
|
+
const isClickable = $derived(Boolean(onClick && dateValue));
|
|
55
|
+
|
|
56
|
+
// Format the date based on options
|
|
57
|
+
const formatDate = (date: Date | null): string => {
|
|
58
|
+
if (!date) return fallback;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Custom formatter takes precedence
|
|
62
|
+
if (customFormat) {
|
|
63
|
+
return customFormat(date);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Built-in format options
|
|
67
|
+
const options: Intl.DateTimeFormatOptions = {};
|
|
68
|
+
|
|
69
|
+
switch (format) {
|
|
70
|
+
case 'short':
|
|
71
|
+
options.dateStyle = 'short';
|
|
72
|
+
break;
|
|
73
|
+
case 'medium':
|
|
74
|
+
options.dateStyle = 'medium';
|
|
75
|
+
break;
|
|
76
|
+
case 'long':
|
|
77
|
+
options.dateStyle = 'long';
|
|
78
|
+
break;
|
|
79
|
+
case 'relative':
|
|
80
|
+
return formatRelativeDate(date);
|
|
81
|
+
default:
|
|
82
|
+
options.dateStyle = 'medium';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (showTime) {
|
|
86
|
+
options.timeStyle = 'short';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (timezone) {
|
|
90
|
+
options.timeZone = timezone;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return new Intl.DateTimeFormat(locale, options).format(date);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.warn('DateCell: Error formatting date', error);
|
|
96
|
+
return fallback;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const formatRelativeDate = (date: Date): string => {
|
|
101
|
+
const now = new Date();
|
|
102
|
+
const diffMs = now.getTime() - date.getTime();
|
|
103
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
104
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
105
|
+
const diffMinutes = Math.floor(diffMs / (1000 * 60));
|
|
106
|
+
|
|
107
|
+
if (Math.abs(diffDays) > 7) {
|
|
108
|
+
return new Intl.DateTimeFormat(locale, { dateStyle: 'short' }).format(date);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
|
112
|
+
|
|
113
|
+
if (diffMs < 0) {
|
|
114
|
+
const futureDays = Math.abs(diffDays);
|
|
115
|
+
const futureHours = Math.abs(diffHours);
|
|
116
|
+
const futureMinutes = Math.abs(diffMinutes);
|
|
117
|
+
|
|
118
|
+
if (futureDays > 0) return rtf.format(futureDays, 'day');
|
|
119
|
+
if (futureHours > 0) return rtf.format(futureHours, 'hour');
|
|
120
|
+
if (futureMinutes > 0) return rtf.format(futureMinutes, 'minute');
|
|
121
|
+
return rtf.format(0, 'second');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (diffDays >= 1) return rtf.format(-diffDays, 'day');
|
|
125
|
+
if (diffHours >= 1) return rtf.format(-diffHours, 'hour');
|
|
126
|
+
if (diffMinutes >= 1) return rtf.format(-diffMinutes, 'minute');
|
|
127
|
+
return rtf.format(0, 'second');
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Get formatted date string
|
|
131
|
+
const formattedDate = $derived.by(() => formatDate(dateValue));
|
|
132
|
+
|
|
133
|
+
// Generate tooltip with full date info
|
|
134
|
+
const tooltipText = $derived.by(() => {
|
|
135
|
+
const date = dateValue;
|
|
136
|
+
if (!date) return undefined;
|
|
137
|
+
|
|
138
|
+
const fullFormat = new Intl.DateTimeFormat(locale, {
|
|
139
|
+
dateStyle: 'full',
|
|
140
|
+
timeStyle: 'medium',
|
|
141
|
+
timeZone: timezone
|
|
142
|
+
}).format(date);
|
|
143
|
+
|
|
144
|
+
return fullFormat;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const tooltipValue = $derived(tooltipText);
|
|
148
|
+
|
|
149
|
+
// Generate test ID
|
|
150
|
+
const computedTestId = $derived.by(() => {
|
|
151
|
+
if (testId) return testId;
|
|
152
|
+
if (item && typeof item === 'object' && 'id' in item) {
|
|
153
|
+
return `date-cell-${item.id}-${String(dateKey)}`;
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// TV Styles
|
|
159
|
+
const styles = $derived(dateCellVariants({ interactive: isClickable, size, format }));
|
|
160
|
+
|
|
161
|
+
// Event handlers
|
|
162
|
+
function handleClick(event: MouseEvent) {
|
|
163
|
+
const date = dateValue;
|
|
164
|
+
if (onClick && date) {
|
|
165
|
+
event.stopPropagation();
|
|
166
|
+
onClick(item, date);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
171
|
+
if (isClickable && (event.key === 'Enter' || event.key === ' ')) {
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
const date = dateValue;
|
|
174
|
+
if (onClick && date) {
|
|
175
|
+
onClick(item, date);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
182
|
+
<div
|
|
183
|
+
class="{styles.container()} {className}"
|
|
184
|
+
title={tooltipValue}
|
|
185
|
+
data-testid={computedTestId}
|
|
186
|
+
onclick={handleClick}
|
|
187
|
+
onkeydown={handleKeyDown}
|
|
188
|
+
role={isClickable ? 'button' : undefined}
|
|
189
|
+
tabindex={isClickable ? 0 : undefined}
|
|
190
|
+
>
|
|
191
|
+
<time datetime={dateValue?.toISOString()} class={styles.date()}>
|
|
192
|
+
{formattedDate}
|
|
193
|
+
</time>
|
|
194
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type DateCellVariantProps } from '../variants';
|
|
2
|
+
export type DateCellProps<Item> = {
|
|
3
|
+
item: Item;
|
|
4
|
+
dateKey?: keyof Item;
|
|
5
|
+
format?: 'short' | 'medium' | 'long' | 'relative' | 'datetime';
|
|
6
|
+
customFormat?: (date: Date) => string;
|
|
7
|
+
locale?: string;
|
|
8
|
+
showTime?: boolean;
|
|
9
|
+
timezone?: string;
|
|
10
|
+
fallback?: string;
|
|
11
|
+
onClick?: (item: Item, date: Date | null) => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
testId?: string;
|
|
14
|
+
size?: DateCellVariantProps['size'];
|
|
15
|
+
};
|
|
16
|
+
declare function $$render<Item>(): {
|
|
17
|
+
props: DateCellProps<Item>;
|
|
18
|
+
exports: {};
|
|
19
|
+
bindings: "";
|
|
20
|
+
slots: {};
|
|
21
|
+
events: {};
|
|
22
|
+
};
|
|
23
|
+
declare class __sveltets_Render<Item> {
|
|
24
|
+
props(): ReturnType<typeof $$render<Item>>['props'];
|
|
25
|
+
events(): ReturnType<typeof $$render<Item>>['events'];
|
|
26
|
+
slots(): ReturnType<typeof $$render<Item>>['slots'];
|
|
27
|
+
bindings(): "";
|
|
28
|
+
exports(): {};
|
|
29
|
+
}
|
|
30
|
+
interface $$IsomorphicComponent {
|
|
31
|
+
new <Item>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Item>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Item>['props']>, ReturnType<__sveltets_Render<Item>['events']>, ReturnType<__sveltets_Render<Item>['slots']>> & {
|
|
32
|
+
$$bindings?: ReturnType<__sveltets_Render<Item>['bindings']>;
|
|
33
|
+
} & ReturnType<__sveltets_Render<Item>['exports']>;
|
|
34
|
+
<Item>(internal: unknown, props: ReturnType<__sveltets_Render<Item>['props']> & {}): ReturnType<__sveltets_Render<Item>['exports']>;
|
|
35
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
36
|
+
}
|
|
37
|
+
declare const DateCell: $$IsomorphicComponent;
|
|
38
|
+
type DateCell<Item> = InstanceType<typeof DateCell<Item>>;
|
|
39
|
+
export default DateCell;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<script lang="ts" generics="Item">
|
|
2
|
+
import { useTableI18n } from '../i18n';
|
|
3
|
+
import { linkCellVariants, type LinkCellVariantProps } from '../variants';
|
|
4
|
+
|
|
5
|
+
const tt = useTableI18n();
|
|
6
|
+
|
|
7
|
+
export type LinkCellProps<Item> = {
|
|
8
|
+
item: Item;
|
|
9
|
+
href?: string | ((item: Item) => string);
|
|
10
|
+
urlKey?: keyof Item;
|
|
11
|
+
text?: string | ((item: Item) => string);
|
|
12
|
+
textKey?: keyof Item;
|
|
13
|
+
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
14
|
+
external?: boolean;
|
|
15
|
+
download?: boolean | string;
|
|
16
|
+
rel?: string;
|
|
17
|
+
onClick?: (item: Item, event: MouseEvent) => void;
|
|
18
|
+
fallback?: string;
|
|
19
|
+
maxLength?: number;
|
|
20
|
+
showIcon?: boolean;
|
|
21
|
+
className?: string;
|
|
22
|
+
testId?: string;
|
|
23
|
+
} & LinkCellVariantProps;
|
|
24
|
+
|
|
25
|
+
// Props with sensible defaults
|
|
26
|
+
let {
|
|
27
|
+
item,
|
|
28
|
+
href = undefined,
|
|
29
|
+
urlKey = undefined,
|
|
30
|
+
text = undefined,
|
|
31
|
+
textKey = undefined,
|
|
32
|
+
target = '_self',
|
|
33
|
+
external = false,
|
|
34
|
+
download = false,
|
|
35
|
+
rel = undefined,
|
|
36
|
+
onClick = undefined,
|
|
37
|
+
fallback = '—',
|
|
38
|
+
maxLength = undefined,
|
|
39
|
+
showIcon = true,
|
|
40
|
+
className = '',
|
|
41
|
+
testId = undefined,
|
|
42
|
+
size = 'md',
|
|
43
|
+
variant = 'default',
|
|
44
|
+
disabled = false
|
|
45
|
+
}: LinkCellProps<Item> = $props();
|
|
46
|
+
|
|
47
|
+
// Extract URL from item or prop
|
|
48
|
+
const extractUrl = (item: Item): string | null => {
|
|
49
|
+
if (typeof href === 'function') {
|
|
50
|
+
return href(item);
|
|
51
|
+
}
|
|
52
|
+
if (typeof href === 'string') {
|
|
53
|
+
return href;
|
|
54
|
+
}
|
|
55
|
+
if (urlKey) {
|
|
56
|
+
const value = item[urlKey];
|
|
57
|
+
return typeof value === 'string' ? value : null;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Extract display text from item or prop
|
|
63
|
+
const extractText = (item: Item): string => {
|
|
64
|
+
if (typeof text === 'function') {
|
|
65
|
+
return text(item);
|
|
66
|
+
}
|
|
67
|
+
if (typeof text === 'string') {
|
|
68
|
+
return text;
|
|
69
|
+
}
|
|
70
|
+
if (textKey) {
|
|
71
|
+
const value = item[textKey];
|
|
72
|
+
if (typeof value === 'string') return value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fallback to URL if no text specified
|
|
76
|
+
const url = extractUrl(item);
|
|
77
|
+
if (url) {
|
|
78
|
+
try {
|
|
79
|
+
const urlObj = new URL(url);
|
|
80
|
+
return urlObj.hostname + urlObj.pathname;
|
|
81
|
+
} catch {
|
|
82
|
+
return url;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return fallback;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Get computed values
|
|
90
|
+
const computedHref = $derived(() => extractUrl(item));
|
|
91
|
+
const computedText = $derived(() => extractText(item));
|
|
92
|
+
|
|
93
|
+
// Determine if link is valid
|
|
94
|
+
const isValidLink = $derived(() => {
|
|
95
|
+
const url = computedHref();
|
|
96
|
+
return url && url.trim().length > 0;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Determine if link should open externally
|
|
100
|
+
const isExternal = $derived(() => {
|
|
101
|
+
if (external) return true;
|
|
102
|
+
if (target === '_blank') return true;
|
|
103
|
+
|
|
104
|
+
const url = computedHref();
|
|
105
|
+
if (!url) return false;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const urlObj = new URL(url);
|
|
109
|
+
return urlObj.origin !== window.location.origin;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Compute rel attribute
|
|
116
|
+
const computedRel = $derived(() => {
|
|
117
|
+
if (rel) return rel;
|
|
118
|
+
if (isExternal()) return 'noopener noreferrer';
|
|
119
|
+
return undefined;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Truncate text if maxLength is specified
|
|
123
|
+
const displayText = $derived.by(() => {
|
|
124
|
+
const text = computedText();
|
|
125
|
+
if (!maxLength || text.length <= maxLength) {
|
|
126
|
+
return text;
|
|
127
|
+
}
|
|
128
|
+
return text.substring(0, maxLength - 3) + '...';
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Generate tooltip
|
|
132
|
+
const tooltipText = $derived.by(() => {
|
|
133
|
+
const url = computedHref();
|
|
134
|
+
const text = computedText();
|
|
135
|
+
|
|
136
|
+
if (!url) return undefined;
|
|
137
|
+
|
|
138
|
+
if (maxLength && text.length > maxLength) {
|
|
139
|
+
return `${text}\n${url}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (text !== url) {
|
|
143
|
+
return url;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return undefined;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const tooltipValue = $derived(tooltipText);
|
|
150
|
+
|
|
151
|
+
// Generate test ID
|
|
152
|
+
const computedTestId = $derived(() => {
|
|
153
|
+
if (testId) return testId;
|
|
154
|
+
if (item && typeof item === 'object' && 'id' in item) {
|
|
155
|
+
return `link-cell-${item.id}`;
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Determine variant based on state
|
|
161
|
+
const linkVariant = $derived.by(() => {
|
|
162
|
+
if (!isValidLink()) return 'default'; // Will be overridden by disabled state in template
|
|
163
|
+
if (isExternal()) return 'external';
|
|
164
|
+
return variant;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const styles = $derived(
|
|
168
|
+
linkCellVariants({ size, variant: linkVariant, disabled: !isValidLink() || disabled })
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Event handlers
|
|
172
|
+
function handleClick(event: MouseEvent) {
|
|
173
|
+
if (onClick) {
|
|
174
|
+
onClick(item, event);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
179
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
const linkElement = event.target as HTMLAnchorElement;
|
|
182
|
+
linkElement.click();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
</script>
|
|
186
|
+
|
|
187
|
+
{#if isValidLink()}
|
|
188
|
+
<div class={styles.container()}>
|
|
189
|
+
<!-- `href` is consumer-provided at runtime; `resolve()` only applies to
|
|
190
|
+
internal SvelteKit routes and would false-positive on any external
|
|
191
|
+
(mailto:/tel:/http) link the consumer configures. -->
|
|
192
|
+
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
|
193
|
+
<a
|
|
194
|
+
href={computedHref()}
|
|
195
|
+
{target}
|
|
196
|
+
rel={computedRel()}
|
|
197
|
+
download={download === true ? true : download || undefined}
|
|
198
|
+
class="{styles.link()} {className}"
|
|
199
|
+
title={tooltipValue}
|
|
200
|
+
data-testid={computedTestId}
|
|
201
|
+
onclick={handleClick}
|
|
202
|
+
onkeydown={handleKeyDown}
|
|
203
|
+
>
|
|
204
|
+
<span class={styles.text()}>
|
|
205
|
+
{displayText}
|
|
206
|
+
</span>
|
|
207
|
+
|
|
208
|
+
{#if showIcon && isExternal()}
|
|
209
|
+
<svg
|
|
210
|
+
class={styles.icon()}
|
|
211
|
+
width="14"
|
|
212
|
+
height="14"
|
|
213
|
+
viewBox="0 0 24 24"
|
|
214
|
+
fill="none"
|
|
215
|
+
stroke="currentColor"
|
|
216
|
+
stroke-width="2"
|
|
217
|
+
stroke-linecap="round"
|
|
218
|
+
stroke-linejoin="round"
|
|
219
|
+
aria-hidden="true"
|
|
220
|
+
>
|
|
221
|
+
<path d="M7 17L17 7" />
|
|
222
|
+
<path d="M7 7h10v10" />
|
|
223
|
+
</svg>
|
|
224
|
+
{/if}
|
|
225
|
+
</a>
|
|
226
|
+
<!-- eslint-enable svelte/no-navigation-without-resolve -->
|
|
227
|
+
</div>
|
|
228
|
+
{:else}
|
|
229
|
+
<div class={styles.container()}>
|
|
230
|
+
<span
|
|
231
|
+
class="{styles.link()} {className}"
|
|
232
|
+
title={tt('table.link.invalid')}
|
|
233
|
+
data-testid={computedTestId}
|
|
234
|
+
>
|
|
235
|
+
<span class={styles.text()}>
|
|
236
|
+
{displayText}
|
|
237
|
+
</span>
|
|
238
|
+
</span>
|
|
239
|
+
</div>
|
|
240
|
+
{/if}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type LinkCellVariantProps } from '../variants';
|
|
2
|
+
export type LinkCellProps<Item> = {
|
|
3
|
+
item: Item;
|
|
4
|
+
href?: string | ((item: Item) => string);
|
|
5
|
+
urlKey?: keyof Item;
|
|
6
|
+
text?: string | ((item: Item) => string);
|
|
7
|
+
textKey?: keyof Item;
|
|
8
|
+
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
9
|
+
external?: boolean;
|
|
10
|
+
download?: boolean | string;
|
|
11
|
+
rel?: string;
|
|
12
|
+
onClick?: (item: Item, event: MouseEvent) => void;
|
|
13
|
+
fallback?: string;
|
|
14
|
+
maxLength?: number;
|
|
15
|
+
showIcon?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
testId?: string;
|
|
18
|
+
} & LinkCellVariantProps;
|
|
19
|
+
declare function $$render<Item>(): {
|
|
20
|
+
props: LinkCellProps<Item>;
|
|
21
|
+
exports: {};
|
|
22
|
+
bindings: "";
|
|
23
|
+
slots: {};
|
|
24
|
+
events: {};
|
|
25
|
+
};
|
|
26
|
+
declare class __sveltets_Render<Item> {
|
|
27
|
+
props(): ReturnType<typeof $$render<Item>>['props'];
|
|
28
|
+
events(): ReturnType<typeof $$render<Item>>['events'];
|
|
29
|
+
slots(): ReturnType<typeof $$render<Item>>['slots'];
|
|
30
|
+
bindings(): "";
|
|
31
|
+
exports(): {};
|
|
32
|
+
}
|
|
33
|
+
interface $$IsomorphicComponent {
|
|
34
|
+
new <Item>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Item>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Item>['props']>, ReturnType<__sveltets_Render<Item>['events']>, ReturnType<__sveltets_Render<Item>['slots']>> & {
|
|
35
|
+
$$bindings?: ReturnType<__sveltets_Render<Item>['bindings']>;
|
|
36
|
+
} & ReturnType<__sveltets_Render<Item>['exports']>;
|
|
37
|
+
<Item>(internal: unknown, props: ReturnType<__sveltets_Render<Item>['props']> & {}): ReturnType<__sveltets_Render<Item>['exports']>;
|
|
38
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
39
|
+
}
|
|
40
|
+
declare const LinkCell: $$IsomorphicComponent;
|
|
41
|
+
type LinkCell<Item> = InstanceType<typeof LinkCell<Item>>;
|
|
42
|
+
export default LinkCell;
|