@rokkit/ui 1.0.0-next.127 → 1.0.0-next.128
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/package.json +6 -16
- package/src/components/BreadCrumbs.svelte +25 -17
- package/src/components/Button.svelte +11 -5
- package/src/components/Carousel.svelte +11 -6
- package/src/components/Code.svelte +6 -2
- package/src/components/FloatingAction.svelte +24 -21
- package/src/components/FloatingNavigation.svelte +36 -29
- package/src/components/Grid.svelte +128 -0
- package/src/components/ItemContent.svelte +21 -20
- package/src/components/LazyTree.svelte +165 -0
- package/src/components/List.svelte +147 -435
- package/src/components/Menu.svelte +195 -346
- package/src/components/MultiSelect.svelte +238 -390
- package/src/components/PaletteManager.svelte +15 -5
- package/src/components/Pill.svelte +19 -14
- package/src/components/Range.svelte +8 -3
- package/src/components/Rating.svelte +19 -9
- package/src/components/SearchFilter.svelte +11 -3
- package/src/components/Select.svelte +265 -454
- package/src/components/Stepper.svelte +9 -6
- package/src/components/Switch.svelte +11 -11
- package/src/components/Table.svelte +0 -1
- package/src/components/Tabs.svelte +96 -172
- package/src/components/Timeline.svelte +5 -5
- package/src/components/Toggle.svelte +55 -119
- package/src/components/Toolbar.svelte +24 -23
- package/src/components/Tree.svelte +115 -584
- package/src/components/UploadFileStatus.svelte +83 -0
- package/src/components/UploadProgress.svelte +131 -0
- package/src/components/UploadTarget.svelte +124 -0
- package/src/components/index.ts +5 -0
- package/src/index.ts +6 -1
- package/src/types/button.ts +3 -0
- package/src/types/code.ts +4 -4
- package/src/types/floating-action.ts +13 -8
- package/src/types/floating-navigation.ts +14 -2
- package/src/types/index.ts +5 -3
- package/src/types/list.ts +10 -6
- package/src/types/menu.ts +38 -138
- package/src/types/palette.ts +17 -0
- package/src/types/select.ts +33 -63
- package/src/types/switch.ts +9 -5
- package/src/types/table.ts +6 -6
- package/src/types/tabs.ts +13 -34
- package/src/types/timeline.ts +5 -3
- package/src/types/toggle.ts +15 -56
- package/src/types/toolbar.ts +1 -1
- package/src/types/tree.ts +9 -18
- package/src/types/upload-file-status.ts +45 -0
- package/src/types/upload-progress.ts +111 -0
- package/src/types/upload-target.ts +68 -0
- package/src/utils/upload.js +128 -0
- package/src/types/item-proxy.ts +0 -358
package/src/types/toggle.ts
CHANGED
|
@@ -2,44 +2,11 @@
|
|
|
2
2
|
* Toggle Component Types
|
|
3
3
|
*
|
|
4
4
|
* Provides types for the data-driven Toggle component.
|
|
5
|
-
* Field mapping and data access is handled by
|
|
5
|
+
* Field mapping and data access is handled by ProxyItem from @rokkit/states.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// =============================================================================
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Field mapping configuration for toggle data.
|
|
14
|
-
* Maps custom data field names to the component's expected properties.
|
|
15
|
-
*/
|
|
16
|
-
export interface ToggleFields {
|
|
17
|
-
/** Field for display text - default: 'text' */
|
|
18
|
-
text?: string
|
|
19
|
-
|
|
20
|
-
/** Field for the value to emit on select - default: 'value' */
|
|
21
|
-
value?: string
|
|
22
|
-
|
|
23
|
-
/** Field for icon class name - default: 'icon' */
|
|
24
|
-
icon?: string
|
|
25
|
-
|
|
26
|
-
/** Field for disabled state - default: 'disabled' */
|
|
27
|
-
disabled?: string
|
|
28
|
-
|
|
29
|
-
/** Field for tooltip/description text - default: 'description' */
|
|
30
|
-
description?: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Default field mapping values
|
|
35
|
-
*/
|
|
36
|
-
export const defaultToggleFields: Required<ToggleFields> = {
|
|
37
|
-
text: 'text',
|
|
38
|
-
value: 'value',
|
|
39
|
-
icon: 'icon',
|
|
40
|
-
disabled: 'disabled',
|
|
41
|
-
description: 'description'
|
|
42
|
-
}
|
|
8
|
+
import type { Snippet } from 'svelte'
|
|
9
|
+
import type { ProxyItem } from '@rokkit/states'
|
|
43
10
|
|
|
44
11
|
// =============================================================================
|
|
45
12
|
// Toggle Item Types
|
|
@@ -55,22 +22,11 @@ export type ToggleItem = Record<string, unknown>
|
|
|
55
22
|
// =============================================================================
|
|
56
23
|
|
|
57
24
|
/**
|
|
58
|
-
*
|
|
25
|
+
* Snippet for rendering a toggle option.
|
|
26
|
+
* The component renders the button wrapper; the snippet renders inner content.
|
|
27
|
+
* Receives the ProxyItem and whether this item is currently selected.
|
|
59
28
|
*/
|
|
60
|
-
export
|
|
61
|
-
/** Call to trigger item selection */
|
|
62
|
-
onclick: () => void
|
|
63
|
-
/** Forward keyboard events for accessibility */
|
|
64
|
-
onkeydown: (event: KeyboardEvent) => void
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Snippet type for rendering toggle items.
|
|
69
|
-
* Fourth parameter is whether the item is currently selected.
|
|
70
|
-
*/
|
|
71
|
-
export type ToggleItemSnippet = import('svelte').Snippet<
|
|
72
|
-
[ToggleItem, ToggleFields, ToggleItemHandlers, boolean]
|
|
73
|
-
>
|
|
29
|
+
export type ToggleItemSnippet = Snippet<[ProxyItem, boolean]>
|
|
74
30
|
|
|
75
31
|
// =============================================================================
|
|
76
32
|
// Component Props Types
|
|
@@ -80,13 +36,13 @@ export type ToggleItemSnippet = import('svelte').Snippet<
|
|
|
80
36
|
* Props for the Toggle component
|
|
81
37
|
*/
|
|
82
38
|
export interface ToggleProps {
|
|
83
|
-
/** Array of toggle options */
|
|
39
|
+
/** Array of toggle options (strings, numbers, or objects) */
|
|
84
40
|
options?: ToggleItem[]
|
|
85
41
|
|
|
86
|
-
/** Field mapping
|
|
87
|
-
fields?:
|
|
42
|
+
/** Field mapping — overrides BASE_FIELDS defaults (text → 'label', value → 'value', …) */
|
|
43
|
+
fields?: Record<string, string>
|
|
88
44
|
|
|
89
|
-
/** Currently selected value */
|
|
45
|
+
/** Currently selected value (bindable) */
|
|
90
46
|
value?: unknown
|
|
91
47
|
|
|
92
48
|
/** Called when selection changes */
|
|
@@ -101,9 +57,12 @@ export interface ToggleProps {
|
|
|
101
57
|
/** Whether the entire toggle is disabled */
|
|
102
58
|
disabled?: boolean
|
|
103
59
|
|
|
60
|
+
/** Accessible label for the radiogroup. Default: messages.current.toggle.label */
|
|
61
|
+
label?: string
|
|
62
|
+
|
|
104
63
|
/** Additional CSS classes */
|
|
105
64
|
class?: string
|
|
106
65
|
|
|
107
|
-
/** Custom snippet for rendering toggle
|
|
66
|
+
/** Custom snippet for rendering toggle options */
|
|
108
67
|
item?: ToggleItemSnippet
|
|
109
68
|
}
|
package/src/types/toolbar.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Toolbar Component Types
|
|
3
3
|
*
|
|
4
4
|
* Provides types for the data-driven Toolbar component.
|
|
5
|
-
* Field mapping and data access is handled by
|
|
5
|
+
* Field mapping and data access is handled by ProxyItem from @rokkit/states.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// =============================================================================
|
package/src/types/tree.ts
CHANGED
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides types for the data-driven Tree component.
|
|
5
5
|
* Supports hierarchical data with expand/collapse, tree lines, and custom icons.
|
|
6
|
-
* Field mapping and data access is handled by
|
|
6
|
+
* Field mapping and data access is handled by ProxyItem from @rokkit/states.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Snippet } from 'svelte'
|
|
10
|
-
import
|
|
11
|
-
import { defaultStateIcons } from '@rokkit/core'
|
|
10
|
+
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
12
11
|
|
|
13
12
|
// =============================================================================
|
|
14
13
|
// Field Mapping Types
|
|
@@ -16,9 +15,9 @@ import { defaultStateIcons } from '@rokkit/core'
|
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Field mapping configuration for tree data.
|
|
19
|
-
* Extends
|
|
18
|
+
* Extends base field mapping with tree-specific fields.
|
|
20
19
|
*/
|
|
21
|
-
export interface TreeFields extends
|
|
20
|
+
export interface TreeFields extends Record<string, string> {
|
|
22
21
|
/** Field for expanded state - default: 'expanded' */
|
|
23
22
|
expanded?: string
|
|
24
23
|
|
|
@@ -72,7 +71,7 @@ export type ConnectorType = 'child' | 'last' | 'sibling' | 'empty'
|
|
|
72
71
|
|
|
73
72
|
/**
|
|
74
73
|
* Icons configuration for tree expand/collapse states.
|
|
75
|
-
* Keys match the naming convention in @rokkit/core
|
|
74
|
+
* Keys match the naming convention in @rokkit/core DEFAULT_STATE_ICONS.
|
|
76
75
|
*/
|
|
77
76
|
export interface TreeStateIcons {
|
|
78
77
|
/** Icon class for expanded/opened state */
|
|
@@ -86,8 +85,8 @@ export interface TreeStateIcons {
|
|
|
86
85
|
* that get resolved to actual icon classes via UnoCSS shortcuts.
|
|
87
86
|
*/
|
|
88
87
|
export const defaultTreeStateIcons: TreeStateIcons = {
|
|
89
|
-
opened:
|
|
90
|
-
closed:
|
|
88
|
+
opened: DEFAULT_STATE_ICONS.node.opened,
|
|
89
|
+
closed: DEFAULT_STATE_ICONS.node.closed
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
// =============================================================================
|
|
@@ -146,8 +145,8 @@ export interface TreeProps {
|
|
|
146
145
|
/** Size variant */
|
|
147
146
|
size?: 'sm' | 'md' | 'lg'
|
|
148
147
|
|
|
149
|
-
/**
|
|
150
|
-
|
|
148
|
+
/** Tree line connector style — 'none' hides lines, 'solid'|'dashed'|'dotted' set the line style */
|
|
149
|
+
lineStyle?: 'none' | 'solid' | 'dashed' | 'dotted'
|
|
151
150
|
|
|
152
151
|
/** Enable multiple item selection (Ctrl+click toggle, Shift+click range) */
|
|
153
152
|
multiselect?: boolean
|
|
@@ -248,12 +247,4 @@ export function getLineTypes(
|
|
|
248
247
|
// Keep old name as alias for backward compatibility
|
|
249
248
|
export const getTreeLineTypes = getLineTypes
|
|
250
249
|
|
|
251
|
-
/**
|
|
252
|
-
* Get the key for a node (for expanded state tracking)
|
|
253
|
-
*/
|
|
254
|
-
export function getNodeKey(proxy: ItemProxy): string {
|
|
255
|
-
const val = proxy.itemValue
|
|
256
|
-
return typeof val === 'string' || typeof val === 'number' ? String(val) : proxy.text
|
|
257
|
-
}
|
|
258
|
-
|
|
259
250
|
export { getSnippet } from './menu.js'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UploadFileStatus Component Types
|
|
3
|
+
*
|
|
4
|
+
* Renders a single file's upload status with progress bar, action buttons
|
|
5
|
+
* (cancel/retry/remove), and file metadata (icon, name, size, status label).
|
|
6
|
+
* Action button visibility driven by cancelWhen/retryWhen/removeWhen arrays.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ProxyItem } from '@rokkit/states'
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Component Props
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Props for the UploadFileStatus component
|
|
17
|
+
*/
|
|
18
|
+
export interface UploadFileStatusProps {
|
|
19
|
+
/** ProxyItem wrapping the file data */
|
|
20
|
+
proxy: ProxyItem
|
|
21
|
+
|
|
22
|
+
/** Status values that show the cancel button */
|
|
23
|
+
cancelWhen?: string[]
|
|
24
|
+
|
|
25
|
+
/** Status values that show the retry button */
|
|
26
|
+
retryWhen?: string[]
|
|
27
|
+
|
|
28
|
+
/** Status values that show the remove button */
|
|
29
|
+
removeWhen?: string[]
|
|
30
|
+
|
|
31
|
+
/** Called when cancel is clicked */
|
|
32
|
+
oncancel?: (proxy: ProxyItem) => void
|
|
33
|
+
|
|
34
|
+
/** Called when retry is clicked */
|
|
35
|
+
onretry?: (proxy: ProxyItem) => void
|
|
36
|
+
|
|
37
|
+
/** Called when remove is clicked */
|
|
38
|
+
onremove?: (proxy: ProxyItem) => void
|
|
39
|
+
|
|
40
|
+
/** Label overrides merged with messages.current.uploadProgress */
|
|
41
|
+
labels?: Record<string, string>
|
|
42
|
+
|
|
43
|
+
/** Icon class overrides for action buttons (cancel, retry, remove) */
|
|
44
|
+
icons?: Record<string, string>
|
|
45
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UploadProgress Component Types
|
|
3
|
+
*
|
|
4
|
+
* File upload status orchestrator. Thin composition layer that renders
|
|
5
|
+
* a header + List or Grid layout. Default itemContent snippet renders
|
|
6
|
+
* UploadFileStatus for each file. Consumer can override with custom snippet.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Snippet } from 'svelte'
|
|
10
|
+
import type { ProxyItem } from '@rokkit/states'
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Item Types
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generic upload file item — any object with mapped fields
|
|
18
|
+
*/
|
|
19
|
+
export type UploadItem = Record<string, unknown>
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Field Mapping Types
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Field mapping for upload file data.
|
|
27
|
+
* Maps semantic field names to keys in the data objects.
|
|
28
|
+
*/
|
|
29
|
+
export interface UploadFields {
|
|
30
|
+
/** Field for display text - default: 'text' */
|
|
31
|
+
label?: string
|
|
32
|
+
|
|
33
|
+
/** Field for unique value - default: 'value' */
|
|
34
|
+
value?: string
|
|
35
|
+
|
|
36
|
+
/** Field for icon class - default: 'icon' */
|
|
37
|
+
icon?: string
|
|
38
|
+
|
|
39
|
+
/** Field for upload status key - default: 'status' */
|
|
40
|
+
status?: string
|
|
41
|
+
|
|
42
|
+
/** Field for progress percentage (0-100) - default: 'progress' */
|
|
43
|
+
progress?: string
|
|
44
|
+
|
|
45
|
+
/** Field for file size in bytes - default: 'size' */
|
|
46
|
+
size?: string
|
|
47
|
+
|
|
48
|
+
/** Field for MIME type - default: 'type' */
|
|
49
|
+
type?: string
|
|
50
|
+
|
|
51
|
+
/** Field for error message - default: 'error' */
|
|
52
|
+
error?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Snippet Types
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Snippet for custom per-file rendering inside List/Grid.
|
|
61
|
+
* Receives the ProxyItem wrapping the file data.
|
|
62
|
+
*/
|
|
63
|
+
export type UploadItemSnippet = Snippet<[proxy: ProxyItem]>
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Component Props
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Props for the UploadProgress component
|
|
71
|
+
*/
|
|
72
|
+
export interface UploadProgressProps {
|
|
73
|
+
/** Array of file data objects */
|
|
74
|
+
files?: UploadItem[]
|
|
75
|
+
|
|
76
|
+
/** Field mapping configuration */
|
|
77
|
+
fields?: UploadFields
|
|
78
|
+
|
|
79
|
+
/** Layout mode */
|
|
80
|
+
view?: 'list' | 'grid'
|
|
81
|
+
|
|
82
|
+
/** Status values that show the cancel button */
|
|
83
|
+
cancelWhen?: string[]
|
|
84
|
+
|
|
85
|
+
/** Status values that show the retry button */
|
|
86
|
+
retryWhen?: string[]
|
|
87
|
+
|
|
88
|
+
/** Status values that show the remove button */
|
|
89
|
+
removeWhen?: string[]
|
|
90
|
+
|
|
91
|
+
/** Called when cancel is clicked (receives raw file object) */
|
|
92
|
+
oncancel?: (file: UploadItem) => void
|
|
93
|
+
|
|
94
|
+
/** Called when retry is clicked (receives raw file object) */
|
|
95
|
+
onretry?: (file: UploadItem) => void
|
|
96
|
+
|
|
97
|
+
/** Called when remove is clicked (receives raw file object) */
|
|
98
|
+
onremove?: (file: UploadItem) => void
|
|
99
|
+
|
|
100
|
+
/** Called when clear all is clicked */
|
|
101
|
+
onclear?: () => void
|
|
102
|
+
|
|
103
|
+
/** Label overrides merged with messages.current.uploadProgress */
|
|
104
|
+
labels?: Record<string, string>
|
|
105
|
+
|
|
106
|
+
/** Additional CSS classes */
|
|
107
|
+
class?: string
|
|
108
|
+
|
|
109
|
+
/** Custom snippet for per-file rendering (overrides default UploadFileStatus) */
|
|
110
|
+
itemContent?: UploadItemSnippet
|
|
111
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UploadTarget Component Types
|
|
3
|
+
*
|
|
4
|
+
* Drop zone with file validation. Accepts drag-and-drop or click-to-browse.
|
|
5
|
+
* Validates files against accept (MIME/extension) and maxSize constraints.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Snippet } from 'svelte'
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Item Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* File validation error returned via onerror callback
|
|
16
|
+
*/
|
|
17
|
+
export interface UploadError {
|
|
18
|
+
/** The file that failed validation */
|
|
19
|
+
file: File
|
|
20
|
+
/** Why the file was rejected */
|
|
21
|
+
reason: 'type' | 'size'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Snippet Types
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Snippet for custom drop zone content.
|
|
30
|
+
* Receives the current dragging state.
|
|
31
|
+
*/
|
|
32
|
+
export type UploadTargetContentSnippet = Snippet<[dragging: boolean]>
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Component Props
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Props for the UploadTarget component
|
|
40
|
+
*/
|
|
41
|
+
export interface UploadTargetProps {
|
|
42
|
+
/** Accepted file types (MIME types or extensions, comma-separated) */
|
|
43
|
+
accept?: string
|
|
44
|
+
|
|
45
|
+
/** Maximum file size in bytes */
|
|
46
|
+
maxSize?: number
|
|
47
|
+
|
|
48
|
+
/** Allow multiple file selection */
|
|
49
|
+
multiple?: boolean
|
|
50
|
+
|
|
51
|
+
/** Disable the drop zone */
|
|
52
|
+
disabled?: boolean
|
|
53
|
+
|
|
54
|
+
/** Label overrides merged with messages.current.uploadTarget */
|
|
55
|
+
labels?: Record<string, string>
|
|
56
|
+
|
|
57
|
+
/** Called with validated files after drop or browse */
|
|
58
|
+
onfiles?: (files: File[]) => void
|
|
59
|
+
|
|
60
|
+
/** Called for each file that fails validation */
|
|
61
|
+
onerror?: (err: UploadError) => void
|
|
62
|
+
|
|
63
|
+
/** Additional CSS classes */
|
|
64
|
+
class?: string
|
|
65
|
+
|
|
66
|
+
/** Custom content snippet (replaces default drop zone UI) */
|
|
67
|
+
content?: UploadTargetContentSnippet
|
|
68
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload utility functions — shared helpers for UploadTarget and UploadProgress.
|
|
3
|
+
* Pure functions with no Svelte dependency.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ARCHIVE_TYPES = new Set(['application/zip', 'application/gzip', 'application/x-tar'])
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if a file matches an accept string (comma-separated MIME types and extensions).
|
|
10
|
+
* @param {{ type: string, name: string }} file
|
|
11
|
+
* @param {string | null | undefined} accept - e.g. 'image/*,.pdf,application/json'
|
|
12
|
+
* @returns {boolean}
|
|
13
|
+
*/
|
|
14
|
+
export function matchesAccept(file, accept) {
|
|
15
|
+
if (!accept) return true
|
|
16
|
+
|
|
17
|
+
const tokens = accept.split(',').map((t) => t.trim()).filter(Boolean)
|
|
18
|
+
if (tokens.length === 0) return true
|
|
19
|
+
|
|
20
|
+
return tokens.some((token) => {
|
|
21
|
+
if (token.startsWith('.')) {
|
|
22
|
+
// Extension match (case-insensitive)
|
|
23
|
+
return file.name.toLowerCase().endsWith(token.toLowerCase())
|
|
24
|
+
}
|
|
25
|
+
if (token.endsWith('/*')) {
|
|
26
|
+
// Wildcard MIME type match (e.g. image/*)
|
|
27
|
+
const prefix = token.slice(0, -1) // 'image/'
|
|
28
|
+
return file.type.startsWith(prefix)
|
|
29
|
+
}
|
|
30
|
+
// Exact MIME type match
|
|
31
|
+
return file.type === token
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate a file against accept and maxSize constraints.
|
|
37
|
+
* @param {{ type: string, name: string, size: number }} file
|
|
38
|
+
* @param {{ accept?: string, maxSize?: number }} constraints
|
|
39
|
+
* @returns {true | { reason: 'type' | 'size' }}
|
|
40
|
+
*/
|
|
41
|
+
export function validateFile(file, { accept, maxSize } = {}) {
|
|
42
|
+
if (accept && !matchesAccept(file, accept)) {
|
|
43
|
+
return { reason: 'type' }
|
|
44
|
+
}
|
|
45
|
+
if (maxSize !== undefined && file.size > maxSize) {
|
|
46
|
+
return { reason: 'size' }
|
|
47
|
+
}
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Map a MIME type to an icon class string.
|
|
53
|
+
* @param {string | null | undefined} mimeType
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
export function inferIcon(mimeType) {
|
|
57
|
+
if (!mimeType) return 'i-lucide:file'
|
|
58
|
+
|
|
59
|
+
if (mimeType.startsWith('image/')) return 'i-lucide:image'
|
|
60
|
+
if (mimeType.startsWith('video/')) return 'i-lucide:video'
|
|
61
|
+
if (mimeType.startsWith('audio/')) return 'i-lucide:music'
|
|
62
|
+
if (mimeType === 'application/pdf') return 'i-lucide:file-text'
|
|
63
|
+
if (mimeType.startsWith('text/')) return 'i-lucide:file-text'
|
|
64
|
+
if (ARCHIVE_TYPES.has(mimeType)) return 'i-lucide:archive'
|
|
65
|
+
|
|
66
|
+
return 'i-lucide:file'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Format a byte count as a human-readable size string.
|
|
71
|
+
* @param {number} bytes
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
export function formatSize(bytes) {
|
|
75
|
+
if (bytes < 1024) return `${bytes} B`
|
|
76
|
+
if (bytes < 1024 ** 2) return `${(bytes / 1024).toFixed(1)} KB`
|
|
77
|
+
if (bytes < 1024 ** 3) return `${(bytes / 1024 ** 2).toFixed(1)} MB`
|
|
78
|
+
return `${(bytes / 1024 ** 3).toFixed(1)} GB`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Group a flat file array by path field into a nested tree structure.
|
|
83
|
+
* Root-level items (empty path) stay ungrouped.
|
|
84
|
+
* Group nodes use `text` as the label field (ProxyItem compatibility).
|
|
85
|
+
*
|
|
86
|
+
* @param {Array<Record<string, any>>} items
|
|
87
|
+
* @param {string} pathField - field name containing the path (e.g. 'path')
|
|
88
|
+
* @param {string} childrenField - field name for children arrays (e.g. 'children')
|
|
89
|
+
* @returns {Array<Record<string, any>>}
|
|
90
|
+
*/
|
|
91
|
+
export function groupByPath(items, pathField, childrenField) {
|
|
92
|
+
if (!items || items.length === 0) return []
|
|
93
|
+
|
|
94
|
+
const root = []
|
|
95
|
+
/** @type {Map<string, Record<string, any>>} */
|
|
96
|
+
const folderMap = new Map()
|
|
97
|
+
|
|
98
|
+
for (const item of items) {
|
|
99
|
+
const rawPath = item[pathField] || ''
|
|
100
|
+
if (!rawPath) {
|
|
101
|
+
root.push(item)
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Split path into segments, filtering empty strings from trailing slashes
|
|
106
|
+
const segments = rawPath.split('/').filter(Boolean)
|
|
107
|
+
let parent = root
|
|
108
|
+
let fullPath = ''
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < segments.length; i++) {
|
|
111
|
+
const segment = segments[i]
|
|
112
|
+
fullPath = fullPath ? `${fullPath}/${segment}` : segment
|
|
113
|
+
|
|
114
|
+
let folder = folderMap.get(fullPath)
|
|
115
|
+
if (!folder) {
|
|
116
|
+
folder = { text: segment, [childrenField]: [] }
|
|
117
|
+
folderMap.set(fullPath, folder)
|
|
118
|
+
parent.push(folder)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
parent = folder[childrenField]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
parent.push(item)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return root
|
|
128
|
+
}
|