@classic-homes/theme-svelte 1.25.1 → 1.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/lib/components/AlertDialog.svelte +14 -6
  2. package/dist/lib/components/AlertDialog.svelte.d.ts +8 -2
  3. package/dist/lib/components/Badge.svelte +6 -2
  4. package/dist/lib/components/CodeField.svelte +24 -8
  5. package/dist/lib/components/CodeField.svelte.d.ts +8 -0
  6. package/dist/lib/components/Combobox.svelte +25 -111
  7. package/dist/lib/components/DataPanel.svelte +31 -24
  8. package/dist/lib/components/DataPanelHeader.svelte +66 -26
  9. package/dist/lib/components/DataTable.svelte +303 -138
  10. package/dist/lib/components/DataTable.svelte.d.ts +37 -13
  11. package/dist/lib/components/DateTimePicker.svelte +159 -34
  12. package/dist/lib/components/Dialog.svelte +14 -6
  13. package/dist/lib/components/Dialog.svelte.d.ts +8 -2
  14. package/dist/lib/components/FileUpload.svelte +23 -6
  15. package/dist/lib/components/FileUpload.svelte.d.ts +7 -2
  16. package/dist/lib/components/MultiSelect.svelte +28 -155
  17. package/dist/lib/components/NumberInput.svelte +28 -110
  18. package/dist/lib/components/OTPInput.svelte +73 -85
  19. package/dist/lib/components/Select.svelte +24 -29
  20. package/dist/lib/components/Signature.svelte +80 -48
  21. package/dist/lib/components/Signature.svelte.d.ts +6 -1
  22. package/dist/lib/components/SkipLink.svelte +7 -2
  23. package/dist/lib/components/SkipLink.svelte.d.ts +2 -0
  24. package/dist/lib/components/Slider.svelte +4 -4
  25. package/dist/lib/components/Toast.svelte +11 -1
  26. package/dist/lib/components/ToastContainer.svelte +38 -24
  27. package/dist/lib/components/Tooltip.svelte +19 -6
  28. package/dist/lib/components/Tooltip.svelte.d.ts +8 -2
  29. package/dist/lib/components/data-table/DataTableColumnFilter.svelte +3 -1
  30. package/dist/lib/components/data-table/DataTableEditableCell.svelte +25 -3
  31. package/dist/lib/components/data-table/DataTableFacetedFilter.svelte +50 -40
  32. package/dist/lib/components/data-table/DataTableFacetedFilter.svelte.d.ts +0 -10
  33. package/dist/lib/components/data-table/DataTableVirtual.svelte +109 -57
  34. package/dist/lib/components/data-table/DataTableVirtual.svelte.d.ts +2 -0
  35. package/dist/lib/components/data-table/createDataTable.svelte.d.ts +26 -4
  36. package/dist/lib/components/data-table/createDataTable.svelte.js +97 -40
  37. package/dist/lib/components/data-table/index.d.ts +1 -1
  38. package/dist/lib/components/data-table/types.d.ts +14 -4
  39. package/dist/lib/components/data-table/types.js +5 -1
  40. package/dist/lib/components/internal/TriggerChild.svelte +60 -0
  41. package/dist/lib/components/internal/TriggerChild.svelte.d.ts +32 -0
  42. package/dist/lib/components/layout/Sidebar.svelte +33 -3
  43. package/dist/lib/components/layout/Sidebar.svelte.d.ts +1 -1
  44. package/dist/lib/components/layout/sidebar/SidebarFlyout.svelte +6 -1
  45. package/dist/lib/components/layout/sidebar/SidebarNavItem.svelte +6 -1
  46. package/dist/lib/components/layout/sidebar/SidebarSection.svelte +6 -1
  47. package/dist/lib/components/media/Audio.svelte +16 -0
  48. package/dist/lib/components/media/Lightbox.svelte +44 -9
  49. package/dist/lib/components/media/Lightbox.svelte.d.ts +9 -2
  50. package/dist/lib/components/media/MediaCarousel.svelte +19 -0
  51. package/dist/lib/components/media/MediaEmbed.svelte +32 -1
  52. package/dist/lib/components/media/Video.svelte +16 -0
  53. package/dist/lib/components/media/internal/MediaControls.svelte +9 -1
  54. package/dist/lib/components/notifications/NotificationBadge.svelte +1 -1
  55. package/dist/lib/components/notifications/NotificationItem.svelte +19 -28
  56. package/dist/lib/composables/useEscapeKey.svelte.d.ts +4 -0
  57. package/dist/lib/composables/useEscapeKey.svelte.js +15 -0
  58. package/dist/lib/composables/useHlsSource.svelte.js +31 -4
  59. package/dist/lib/composables/useMediaPlayer.svelte.js +38 -9
  60. package/dist/lib/composables/useMediaZoom.svelte.js +32 -0
  61. package/dist/lib/composables/usePersistedForm.svelte.d.ts +6 -2
  62. package/dist/lib/composables/usePersistedForm.svelte.js +7 -7
  63. package/dist/lib/composables/useReducedMotion.svelte.js +6 -1
  64. package/dist/lib/composables/useRovingTabindex.svelte.js +12 -0
  65. package/dist/lib/index.d.ts +1 -1
  66. package/dist/lib/stores/toast.svelte.d.ts +13 -0
  67. package/dist/lib/stores/toast.svelte.js +36 -5
  68. package/dist/lib/utils/media.d.ts +7 -0
  69. package/dist/lib/utils/media.js +18 -3
  70. package/package.json +1 -1
@@ -18,15 +18,20 @@
18
18
  * Content of the skip link. Defaults to "Skip to main content".
19
19
  */
20
20
  children?: Snippet;
21
+ /** Runs before the skip; `preventDefault()` cancels the focus move */
22
+ onclick?: (e: MouseEvent) => void;
21
23
  /**
22
24
  * Additional attributes
23
25
  */
24
26
  [key: string]: unknown;
25
27
  }
26
28
 
27
- let { targetId, class: className, children, ...restProps }: Props = $props();
29
+ let { targetId, class: className, children, onclick, ...restProps }: Props = $props();
28
30
 
29
31
  function handleClick(e: MouseEvent) {
32
+ // A consumer onclick is chained, not spread over this handler
33
+ onclick?.(e);
34
+ if (e.defaultPrevented) return;
30
35
  e.preventDefault();
31
36
  const target = document.getElementById(targetId);
32
37
  if (target) {
@@ -58,7 +63,7 @@
58
63
  ...
59
64
  </main>
60
65
  -->
61
- <a href={`#${targetId}`} class={cn('skip-link', className)} onclick={handleClick} {...restProps}>
66
+ <a href={`#${targetId}`} class={cn('skip-link', className)} {...restProps} onclick={handleClick}>
62
67
  {#if children}
63
68
  {@render children()}
64
69
  {:else}
@@ -15,6 +15,8 @@ interface Props {
15
15
  * Content of the skip link. Defaults to "Skip to main content".
16
16
  */
17
17
  children?: Snippet;
18
+ /** Runs before the skip; `preventDefault()` cancels the focus move */
19
+ onclick?: (e: MouseEvent) => void;
18
20
  /**
19
21
  * Additional attributes
20
22
  */
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { Slider as SliderPrimitive } from 'bits-ui';
3
3
  import { cn } from '../utils.js';
4
- import { generateId, computeDescribedBy } from '../utils/form.js';
4
+ import { computeDescribedBy } from '../utils/form.js';
5
5
  import { tv, type VariantProps } from 'tailwind-variants';
6
6
  import Label from './Label.svelte';
7
7
 
@@ -88,9 +88,9 @@
88
88
  class: className,
89
89
  }: Props = $props();
90
90
 
91
- // Generate unique IDs for accessibility
92
- const randomSuffix = generateId('slider').split('-').pop();
93
- const componentId = $derived(`slider-${randomSuffix}`);
91
+ // Generate unique IDs for accessibility ($props.id() is SSR-stable)
92
+ const uid = $props.id();
93
+ const componentId = $derived(`slider-${uid}`);
94
94
  const hintId = $derived(`${componentId}-hint`);
95
95
  const errorId = $derived(`${componentId}-error`);
96
96
 
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import { fly } from 'svelte/transition';
4
4
  import { cn } from '../utils.js';
5
+ import { useReducedMotion } from '../composables/useReducedMotion.svelte.js';
5
6
  import { tv, type VariantProps } from 'tailwind-variants';
6
7
 
7
8
  type ToastType = 'success' | 'error' | 'warning' | 'info';
@@ -63,10 +64,19 @@
63
64
  }: Props = $props();
64
65
 
65
66
  const classes = $derived(cn(toastVariants({ type }), className));
67
+
68
+ // Svelte transitions run as Web Animations, which the CSS reduced-motion
69
+ // override does not reach, so gate the fly here
70
+ const reducedMotion = useReducedMotion();
66
71
  const toastType = $derived(type as ToastType);
67
72
  </script>
68
73
 
69
- <div class={classes} role="alert" transition:fly={{ y: 50, duration: 300 }} {...restProps}>
74
+ <div
75
+ class={classes}
76
+ role="alert"
77
+ transition:fly={{ y: reducedMotion.value ? 0 : 50, duration: reducedMotion.value ? 0 : 300 }}
78
+ {...restProps}
79
+ >
70
80
  <svg
71
81
  class="h-5 w-5 flex-shrink-0 {iconColors[toastType]}"
72
82
  fill="none"
@@ -30,27 +30,41 @@
30
30
  const stackOrder = $derived(position.startsWith('bottom') ? 'flex-col' : 'flex-col-reverse');
31
31
  </script>
32
32
 
33
- {#if toastStore.toasts.length > 0}
34
- <div
35
- class={cn(
36
- 'fixed z-50 flex w-full max-w-sm gap-2 pointer-events-none',
37
- stackOrder,
38
- positionClasses[position],
39
- className
40
- )}
41
- aria-live="polite"
42
- aria-atomic="true"
43
- {...restProps}
44
- >
45
- {#each toastStore.toasts as toast (toast.id)}
46
- <div class="pointer-events-auto">
47
- <Toast
48
- type={toast.type}
49
- title={toast.title}
50
- message={toast.message}
51
- onclose={() => toastStore.remove(toast.id)}
52
- />
53
- </div>
54
- {/each}
55
- </div>
56
- {/if}
33
+ <!-- The live region is always mounted: one that appears together with its
34
+ first toast is not announced. Only additions are read, not the whole stack
35
+ again. Errors are role="alert" (assertive); the rest are read politely. -->
36
+ <div
37
+ class={cn(
38
+ 'fixed z-50 flex w-full max-w-sm gap-2 pointer-events-none',
39
+ stackOrder,
40
+ positionClasses[position],
41
+ className
42
+ )}
43
+ role="region"
44
+ aria-live="polite"
45
+ aria-relevant="additions text"
46
+ aria-label="Notifications"
47
+ {...restProps}
48
+ >
49
+ {#each toastStore.toasts as toast (toast.id)}
50
+ <!-- Hover or focus holds the auto-dismiss timer (WCAG 2.2.1) -->
51
+ <div
52
+ class="pointer-events-auto"
53
+ role="presentation"
54
+ onmouseenter={() => toastStore.pause(toast.id)}
55
+ onmouseleave={() => toastStore.resume(toast.id)}
56
+ onfocusin={() => toastStore.pause(toast.id)}
57
+ onfocusout={(e) => {
58
+ if (!e.currentTarget.contains(e.relatedTarget as Node | null)) toastStore.resume(toast.id);
59
+ }}
60
+ >
61
+ <Toast
62
+ type={toast.type}
63
+ title={toast.title}
64
+ message={toast.message}
65
+ role={toast.type === 'error' ? 'alert' : undefined}
66
+ onclose={() => toastStore.remove(toast.id)}
67
+ />
68
+ </div>
69
+ {/each}
70
+ </div>
@@ -2,6 +2,7 @@
2
2
  import { Tooltip as TooltipPrimitive } from 'bits-ui';
3
3
  import { cn } from '../utils.js';
4
4
  import type { Snippet } from 'svelte';
5
+ import TriggerChild, { type TriggerSnippetProps } from './internal/TriggerChild.svelte';
5
6
 
6
7
  interface Props {
7
8
  /** Content to display in the tooltip */
@@ -14,8 +15,13 @@
14
15
  delayDuration?: number;
15
16
  /** Additional class for tooltip content */
16
17
  class?: string;
17
- /** Trigger element */
18
- children: Snippet;
18
+ /**
19
+ * Trigger element. It receives `{ props }`: spread them on your own
20
+ * focusable element (`<Button {...props}>`) so it is the one that opens
21
+ * and is described by the tooltip. A snippet that ignores them still
22
+ * works, wrapped in a focusable span as before.
23
+ */
24
+ children: Snippet<[TriggerSnippetProps]>;
19
25
  }
20
26
 
21
27
  let {
@@ -26,15 +32,22 @@
26
32
  class: className,
27
33
  children,
28
34
  }: Props = $props();
35
+
36
+ const uid = $props.id();
37
+ // Changes once if the trigger snippet ignores its props (see TriggerChild)
38
+ let triggerId = $state(`${uid}-trigger`);
29
39
  </script>
30
40
 
31
41
  <TooltipPrimitive.Provider>
32
42
  <TooltipPrimitive.Root {delayDuration}>
33
- <TooltipPrimitive.Trigger>
43
+ <TooltipPrimitive.Trigger id={triggerId}>
34
44
  {#snippet child({ props })}
35
- <span {...props} class="inline-flex">
36
- {@render children()}
37
- </span>
45
+ <TriggerChild
46
+ {props}
47
+ trigger={children}
48
+ fallbackClass="inline-flex"
49
+ onfallback={() => (triggerId = `${uid}-trigger-wrap`)}
50
+ />
38
51
  {/snippet}
39
52
  </TooltipPrimitive.Trigger>
40
53
  <TooltipPrimitive.Portal>
@@ -1,4 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import { type TriggerSnippetProps } from './internal/TriggerChild.svelte';
2
3
  interface Props {
3
4
  /** Content to display in the tooltip */
4
5
  content: string;
@@ -10,8 +11,13 @@ interface Props {
10
11
  delayDuration?: number;
11
12
  /** Additional class for tooltip content */
12
13
  class?: string;
13
- /** Trigger element */
14
- children: Snippet;
14
+ /**
15
+ * Trigger element. It receives `{ props }`: spread them on your own
16
+ * focusable element (`<Button {...props}>`) so it is the one that opens
17
+ * and is described by the tooltip. A snippet that ignores them still
18
+ * works, wrapped in a focusable span as before.
19
+ */
20
+ children: Snippet<[TriggerSnippetProps]>;
15
21
  }
16
22
  declare const Tooltip: import("svelte").Component<Props, {}, "">;
17
23
  type Tooltip = ReturnType<typeof Tooltip>;
@@ -45,7 +45,9 @@
45
45
  // Sync with external filter changes (controlled mode)
46
46
  // This also handles initial value synchronization
47
47
  $effect(() => {
48
- const externalFilter = table.columnFilters.get(columnId) ?? '';
48
+ const raw = table.columnFilters.get(columnId);
49
+ // A faceted (array) filter has no text form; leave the input empty
50
+ const externalFilter = typeof raw === 'string' ? raw : '';
49
51
  // Only sync if not currently typing (debounce timer not active)
50
52
  // Always sync on first run to get initial value
51
53
  if (!isInitialized || (!debounceTimer && externalFilter !== inputValue)) {
@@ -3,7 +3,9 @@
3
3
  * DataTableEditableCell - Inline editable cell component
4
4
  *
5
5
  * Allows users to edit cell values in place.
6
- * Supports text, number, and select inputs.
6
+ * Numeric cells (a number value, or `meta.editType: 'number'`) get a number
7
+ * input and save a number (or null when emptied); other cells save a string.
8
+ * `onSave` only fires when the value actually changed.
7
9
  * Works with the native Svelte 5 DataTable implementation.
8
10
  */
9
11
  import { tick } from 'svelte';
@@ -28,6 +30,10 @@
28
30
  const meta = $derived(column.meta as Record<string, unknown> | undefined);
29
31
  const isEditable = $derived(column.enableEditing === true || meta?.enableEditing === true);
30
32
 
33
+ const isNumeric = $derived(
34
+ meta?.editType === 'number' || typeof getColumnValue(row.original, column) === 'number'
35
+ );
36
+
31
37
  let isEditing = $state(false);
32
38
  let editValue = $state<string>('');
33
39
  let buttonEl = $state<HTMLButtonElement | null>(null);
@@ -61,10 +67,26 @@
61
67
  isEditing = true;
62
68
  }
63
69
 
70
+ /** The edited text as the column's type; undefined when it is not valid. */
71
+ function parseEditValue(): unknown {
72
+ if (!isNumeric) return editValue;
73
+ if (!editValue.trim()) return null;
74
+ const n = Number(editValue);
75
+ return Number.isFinite(n) ? n : undefined;
76
+ }
77
+
64
78
  function saveEdit() {
65
79
  if (!isEditing) return;
66
- onSave(row.id, column.id, editValue, row.original);
67
80
  isEditing = false;
81
+ const original = getColumnValue(row.original, column);
82
+ const next = parseEditValue();
83
+ // Invalid input keeps the old value; an unchanged one is not a save
84
+ if (next === undefined) return;
85
+ const unchanged = isNumeric
86
+ ? Object.is(next, original ?? null)
87
+ : next === String(original ?? '');
88
+ if (unchanged) return;
89
+ onSave(row.id, column.id, next, row.original);
68
90
  }
69
91
 
70
92
  function cancelEdit() {
@@ -107,7 +129,7 @@
107
129
  <div class={cn('relative w-full', className)}>
108
130
  <input
109
131
  use:autofocus
110
- type="text"
132
+ type={isNumeric ? 'number' : 'text'}
111
133
  value={editValue}
112
134
  oninput={(e: Event) => (editValue = (e.target as HTMLInputElement).value)}
113
135
  onkeydown={handleKeydown}
@@ -2,17 +2,18 @@
2
2
  /**
3
3
  * DataTableFacetedFilter - Multi-select filter for categorical columns
4
4
  *
5
- * Allows filtering by selecting multiple values from a list.
6
- * Works with the native Svelte 5 DataTable implementation.
5
+ * Allows filtering by selecting multiple values from a list. The column
6
+ * filter is stored as a string[] and matched exactly, so "active" never
7
+ * matches "inactive". A legacy comma-separated string is still read.
7
8
  *
8
- * Note: This component manages its own filter state independently,
9
- * suitable for use cases where you want faceted filtering separate
10
- * from the main table's column filter state.
9
+ * The options open in a Bits UI popover; each option is a single
10
+ * `role="checkbox"` button (no nested controls).
11
11
  */
12
+ import { Popover } from 'bits-ui';
12
13
  import type { DataTableInstance } from './createDataTable.svelte.js';
13
14
  import Button from '../Button.svelte';
14
15
  import Badge from '../Badge.svelte';
15
- import Checkbox from '../Checkbox.svelte';
16
+ import { buttonVariants } from '../button-variants.js';
16
17
  import { cn } from '../../utils.js';
17
18
 
18
19
  interface FilterOption {
@@ -36,11 +37,12 @@
36
37
 
37
38
  let { table, columnId, title, options, class: className }: Props = $props();
38
39
 
39
- // Get current selected values from column filter
40
+ // Current selected values from the column filter
40
41
  const selectedValues = $derived.by(() => {
41
42
  const filterValue = table.columnFilters.get(columnId);
42
43
  if (!filterValue) return new Set<string>();
43
- // Parse comma-separated values if that's how we store them
44
+ if (Array.isArray(filterValue)) return new Set(filterValue);
45
+ // Legacy: comma-separated string
44
46
  return new Set(filterValue.split(',').filter(Boolean));
45
47
  });
46
48
 
@@ -53,34 +55,19 @@
53
55
  } else {
54
56
  newValues.add(value);
55
57
  }
56
- // Store as comma-separated string
57
- table.setColumnFilter(columnId, Array.from(newValues).join(','));
58
+ table.setColumnFilter(columnId, Array.from(newValues));
58
59
  }
59
60
 
60
61
  function clearAll() {
61
- table.setColumnFilter(columnId, '');
62
- }
63
-
64
- function handleClickOutside(event: MouseEvent) {
65
- const target = event.target as HTMLElement;
66
- if (!target.closest('.faceted-filter-container')) {
67
- isOpen = false;
68
- }
62
+ table.setColumnFilter(columnId, []);
69
63
  }
70
64
 
71
65
  const selectedCount = $derived(selectedValues.size);
72
66
  </script>
73
67
 
74
- <svelte:window onclick={isOpen ? handleClickOutside : undefined} />
75
-
76
- <div class={cn('faceted-filter-container relative', className)}>
77
- <Button
78
- variant="outline"
79
- size="sm"
80
- class="border-dashed"
81
- onclick={() => (isOpen = !isOpen)}
82
- aria-expanded={isOpen}
83
- aria-haspopup="listbox"
68
+ <Popover.Root bind:open={isOpen}>
69
+ <Popover.Trigger
70
+ class={cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'border-dashed', className)}
84
71
  aria-label={`Filter by ${title}`}
85
72
  >
86
73
  <svg
@@ -103,23 +90,46 @@
103
90
  {selectedCount}
104
91
  </Badge>
105
92
  {/if}
106
- </Button>
107
-
108
- {#if isOpen}
109
- <div
110
- class="absolute left-0 top-full z-50 mt-2 min-w-[200px] rounded-md border border-black bg-popover p-2 shadow-md"
93
+ </Popover.Trigger>
94
+ <Popover.Portal>
95
+ <Popover.Content
96
+ align="start"
97
+ sideOffset={8}
98
+ collisionPadding={8}
99
+ class="z-50 min-w-[200px] rounded-md border bg-popover p-2 text-popover-foreground shadow-md"
111
100
  >
112
- <div class="space-y-1">
101
+ <div class="space-y-1" role="group" aria-label={title}>
113
102
  {#each options as option (option.value)}
103
+ {@const checked = selectedValues.has(option.value)}
114
104
  <button
115
105
  type="button"
106
+ role="checkbox"
107
+ aria-checked={checked}
116
108
  class={cn(
117
- 'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent',
118
- selectedValues.has(option.value) && 'bg-accent'
109
+ 'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
110
+ checked && 'bg-accent'
119
111
  )}
120
112
  onclick={() => toggleOption(option.value)}
121
113
  >
122
- <Checkbox checked={selectedValues.has(option.value)} />
114
+ <span
115
+ class={cn(
116
+ 'flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border border-primary',
117
+ checked && 'bg-primary text-primary-foreground'
118
+ )}
119
+ aria-hidden="true"
120
+ >
121
+ {#if checked}
122
+ <svg
123
+ class="h-3 w-3"
124
+ fill="none"
125
+ viewBox="0 0 24 24"
126
+ stroke="currentColor"
127
+ stroke-width="3"
128
+ >
129
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
130
+ </svg>
131
+ {/if}
132
+ </span>
123
133
  <span class="flex-1 text-left">{option.label}</span>
124
134
  {#if option.count !== undefined}
125
135
  <span class="text-muted-foreground">{option.count}</span>
@@ -133,6 +143,6 @@
133
143
  <Button variant="ghost" size="sm" class="w-full" onclick={clearAll}>Clear filters</Button>
134
144
  </div>
135
145
  {/if}
136
- </div>
137
- {/if}
138
- </div>
146
+ </Popover.Content>
147
+ </Popover.Portal>
148
+ </Popover.Root>
@@ -1,13 +1,3 @@
1
- /**
2
- * DataTableFacetedFilter - Multi-select filter for categorical columns
3
- *
4
- * Allows filtering by selecting multiple values from a list.
5
- * Works with the native Svelte 5 DataTable implementation.
6
- *
7
- * Note: This component manages its own filter state independently,
8
- * suitable for use cases where you want faceted filtering separate
9
- * from the main table's column filter state.
10
- */
11
1
  import type { DataTableInstance } from './createDataTable.svelte.js';
12
2
  declare function $$render<TData extends object>(): {
13
3
  props: {