@inkra/react 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +316 -2
- package/THIRD_PARTY_NOTICES.md +39 -0
- package/Yozai-OFL.txt +94 -0
- package/dist/Button.d.ts +36 -0
- package/dist/Button.js +89 -0
- package/dist/PencilIcon.d.ts +22 -0
- package/dist/PencilIcon.js +210 -0
- package/dist/PencilLine.d.ts +10 -0
- package/dist/PencilLine.js +91 -0
- package/dist/PencilSurface.d.ts +19 -0
- package/dist/PencilSurface.js +19 -0
- package/dist/PencilText.d.ts +16 -0
- package/dist/PencilText.js +338 -0
- package/dist/accordion/Accordion.d.ts +27 -0
- package/dist/accordion/Accordion.js +40 -0
- package/dist/accordion/index.d.ts +2 -0
- package/dist/accordion/index.js +1 -0
- package/dist/alert-dialog/index.d.ts +64 -0
- package/dist/alert-dialog/index.js +109 -0
- package/dist/assets/ui-hand-yozai.woff2 +0 -0
- package/dist/audio-player/index.d.ts +60 -0
- package/dist/audio-player/index.js +101 -0
- package/dist/breadcrumb/index.d.ts +16 -0
- package/dist/breadcrumb/index.js +25 -0
- package/dist/calendar/index.d.ts +21 -0
- package/dist/calendar/index.js +41 -0
- package/dist/carousel/index.d.ts +51 -0
- package/dist/carousel/index.js +290 -0
- package/dist/choice-grid/index.d.ts +38 -0
- package/dist/choice-grid/index.js +85 -0
- package/dist/color-picker/color.d.ts +15 -0
- package/dist/color-picker/color.js +55 -0
- package/dist/color-picker/index.d.ts +31 -0
- package/dist/color-picker/index.js +69 -0
- package/dist/command/index.d.ts +23 -0
- package/dist/command/index.js +39 -0
- package/dist/content/index.d.ts +106 -0
- package/dist/content/index.js +80 -0
- package/dist/context-menu/index.d.ts +76 -0
- package/dist/context-menu/index.js +240 -0
- package/dist/data-table/index.d.ts +47 -0
- package/dist/data-table/index.js +91 -0
- package/dist/date-picker/index.d.ts +50 -0
- package/dist/date-picker/index.js +180 -0
- package/dist/day-agenda/index.d.ts +43 -0
- package/dist/day-agenda/index.js +47 -0
- package/dist/day-agenda/layout.d.ts +20 -0
- package/dist/day-agenda/layout.js +60 -0
- package/dist/display/ProgressRing.d.ts +6 -0
- package/dist/display/ProgressRing.js +146 -0
- package/dist/display/index.d.ts +59 -0
- package/dist/display/index.js +75 -0
- package/dist/dockable-panel/index.d.ts +41 -0
- package/dist/dockable-panel/index.js +253 -0
- package/dist/dockable-panel/model.d.ts +21 -0
- package/dist/dockable-panel/model.js +61 -0
- package/dist/drawer/index.d.ts +63 -0
- package/dist/drawer/index.js +86 -0
- package/dist/dropdown-menu/index.d.ts +34 -0
- package/dist/dropdown-menu/index.js +40 -0
- package/dist/editable/index.d.ts +53 -0
- package/dist/editable/index.js +162 -0
- package/dist/empty/index.d.ts +21 -0
- package/dist/empty/index.js +23 -0
- package/dist/flip/index.d.ts +49 -0
- package/dist/flip/index.js +138 -0
- package/dist/form/EditablePencilText.d.ts +13 -0
- package/dist/form/EditablePencilText.js +323 -0
- package/dist/form/Field.d.ts +13 -0
- package/dist/form/Field.js +26 -0
- package/dist/form/Input.d.ts +27 -0
- package/dist/form/Input.js +97 -0
- package/dist/form/InputAction.d.ts +7 -0
- package/dist/form/InputAction.js +24 -0
- package/dist/form/Textarea.d.ts +17 -0
- package/dist/form/Textarea.js +102 -0
- package/dist/form/context.d.ts +12 -0
- package/dist/form/context.js +10 -0
- package/dist/form/controlProps.d.ts +23 -0
- package/dist/form/controlProps.js +20 -0
- package/dist/form/index.d.ts +7 -0
- package/dist/form/index.js +3 -0
- package/dist/form/types.d.ts +6 -0
- package/dist/form/types.js +1 -0
- package/dist/heatmap/index.d.ts +59 -0
- package/dist/heatmap/index.js +248 -0
- package/dist/hotkey/index.d.ts +37 -0
- package/dist/hotkey/index.js +130 -0
- package/dist/hotkey/model.d.ts +27 -0
- package/dist/hotkey/model.js +97 -0
- package/dist/hover-card/index.d.ts +33 -0
- package/dist/hover-card/index.js +141 -0
- package/dist/image-cropper/index.d.ts +82 -0
- package/dist/image-cropper/index.js +261 -0
- package/dist/image-cropper/model.d.ts +42 -0
- package/dist/image-cropper/model.js +94 -0
- package/dist/image-viewer/index.d.ts +79 -0
- package/dist/image-viewer/index.js +220 -0
- package/dist/image-viewer/model.d.ts +9 -0
- package/dist/image-viewer/model.js +18 -0
- package/dist/images/layers-2x.png +0 -0
- package/dist/images/layers.png +0 -0
- package/dist/images/marker-icon.png +0 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +69 -0
- package/dist/input-number/index.d.ts +16 -0
- package/dist/input-number/index.js +77 -0
- package/dist/internal/InputActionContext.d.ts +1 -0
- package/dist/internal/InputActionContext.js +3 -0
- package/dist/internal/cache.d.ts +14 -0
- package/dist/internal/cache.js +54 -0
- package/dist/internal/line.d.ts +4 -0
- package/dist/internal/line.js +21 -0
- package/dist/internal/paper.d.ts +1 -0
- package/dist/internal/paper.js +65 -0
- package/dist/internal/random.d.ts +3 -0
- package/dist/internal/random.js +30 -0
- package/dist/internal/recovery.d.ts +1 -0
- package/dist/internal/recovery.js +52 -0
- package/dist/internal/renderTokens.d.ts +10 -0
- package/dist/internal/renderTokens.js +13 -0
- package/dist/internal/stroke.d.ts +24 -0
- package/dist/internal/stroke.js +144 -0
- package/dist/internal/surfaceRenderer.d.ts +41 -0
- package/dist/internal/surfaceRenderer.js +178 -0
- package/dist/internal/textQueue.d.ts +1 -0
- package/dist/internal/textQueue.js +37 -0
- package/dist/internal/textRenderer.d.ts +28 -0
- package/dist/internal/textRenderer.js +239 -0
- package/dist/internal/travelIcons.d.ts +24 -0
- package/dist/internal/travelIcons.js +14 -0
- package/dist/internal/useBrowserLayoutEffect.d.ts +2 -0
- package/dist/internal/useBrowserLayoutEffect.js +3 -0
- package/dist/internal/usePencilSurface.d.ts +3 -0
- package/dist/internal/usePencilSurface.js +116 -0
- package/dist/internal/useSelectionDrawing.d.ts +2 -0
- package/dist/internal/useSelectionDrawing.js +90 -0
- package/dist/internal/wash.d.ts +16 -0
- package/dist/internal/wash.js +149 -0
- package/dist/journey-map/MapScale.d.ts +4 -0
- package/dist/journey-map/MapScale.js +54 -0
- package/dist/journey-map/index.d.ts +39 -0
- package/dist/journey-map/index.js +197 -0
- package/dist/journey-map/terrain.d.ts +8 -0
- package/dist/journey-map/terrain.js +127 -0
- package/dist/keypad/index.d.ts +23 -0
- package/dist/keypad/index.js +63 -0
- package/dist/keypad/model.d.ts +20 -0
- package/dist/keypad/model.js +33 -0
- package/dist/layout/index.d.ts +63 -0
- package/dist/layout/index.js +62 -0
- package/dist/log/index.d.ts +45 -0
- package/dist/log/index.js +159 -0
- package/dist/log/model.d.ts +16 -0
- package/dist/log/model.js +13 -0
- package/dist/node-graph/index.d.ts +123 -0
- package/dist/node-graph/index.js +304 -0
- package/dist/number-ticker/index.d.ts +29 -0
- package/dist/number-ticker/index.js +132 -0
- package/dist/number-ticker/model.d.ts +8 -0
- package/dist/number-ticker/model.js +34 -0
- package/dist/overflow-list/index.d.ts +39 -0
- package/dist/overflow-list/index.js +230 -0
- package/dist/overflow-list/layout.d.ts +19 -0
- package/dist/overflow-list/layout.js +44 -0
- package/dist/overlay/Overlay.d.ts +65 -0
- package/dist/overlay/Overlay.js +77 -0
- package/dist/overlay/Popover.d.ts +43 -0
- package/dist/overlay/Popover.js +45 -0
- package/dist/overlay/index.d.ts +4 -0
- package/dist/overlay/index.js +2 -0
- package/dist/overlay/themeBridge.d.ts +8 -0
- package/dist/overlay/themeBridge.js +98 -0
- package/dist/pagination/index.d.ts +23 -0
- package/dist/pagination/index.js +41 -0
- package/dist/pagination/model.d.ts +4 -0
- package/dist/pagination/model.js +42 -0
- package/dist/pan-zoom/index.d.ts +71 -0
- package/dist/pan-zoom/index.js +175 -0
- package/dist/pencil.d.ts +10 -0
- package/dist/pencil.js +39 -0
- package/dist/radar/index.d.ts +54 -0
- package/dist/radar/index.js +220 -0
- package/dist/range-slider/index.d.ts +16 -0
- package/dist/range-slider/index.js +22 -0
- package/dist/rating/index.d.ts +28 -0
- package/dist/rating/index.js +127 -0
- package/dist/resizable/index.d.ts +32 -0
- package/dist/resizable/index.js +50 -0
- package/dist/scroll-area/index.d.ts +36 -0
- package/dist/scroll-area/index.js +44 -0
- package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
- package/dist/segmented-progress/SegmentedProgress.js +42 -0
- package/dist/select/MultipleSelect.d.ts +44 -0
- package/dist/select/MultipleSelect.js +124 -0
- package/dist/select/SearchableSelect.d.ts +45 -0
- package/dist/select/SearchableSelect.js +50 -0
- package/dist/select/Select.d.ts +109 -0
- package/dist/select/Select.js +205 -0
- package/dist/select/index.d.ts +4 -0
- package/dist/select/index.js +2 -0
- package/dist/selection/Checkbox.d.ts +9 -0
- package/dist/selection/Checkbox.js +37 -0
- package/dist/selection/RadioGroup.d.ts +23 -0
- package/dist/selection/RadioGroup.js +82 -0
- package/dist/selection/Switch.d.ts +8 -0
- package/dist/selection/Switch.js +36 -0
- package/dist/selection/index.d.ts +6 -0
- package/dist/selection/index.js +3 -0
- package/dist/selection/useNativeChecked.d.ts +13 -0
- package/dist/selection/useNativeChecked.js +27 -0
- package/dist/slider/index.d.ts +11 -0
- package/dist/slider/index.js +45 -0
- package/dist/slot/index.d.ts +24 -0
- package/dist/slot/index.js +29 -0
- package/dist/sortable/index.d.ts +50 -0
- package/dist/sortable/index.js +278 -0
- package/dist/sortable/model.d.ts +30 -0
- package/dist/sortable/model.js +64 -0
- package/dist/sparkline/index.d.ts +19 -0
- package/dist/sparkline/index.js +240 -0
- package/dist/sparkline/model.d.ts +16 -0
- package/dist/sparkline/model.js +28 -0
- package/dist/steps/index.d.ts +16 -0
- package/dist/steps/index.js +37 -0
- package/dist/styles.css +8226 -0
- package/dist/tabs/index.d.ts +22 -0
- package/dist/tabs/index.js +42 -0
- package/dist/tag-input/index.d.ts +69 -0
- package/dist/tag-input/index.js +325 -0
- package/dist/tag-input/model.d.ts +14 -0
- package/dist/tag-input/model.js +38 -0
- package/dist/time-picker/index.d.ts +15 -0
- package/dist/time-picker/index.js +168 -0
- package/dist/timeline/index.d.ts +14 -0
- package/dist/timeline/index.js +16 -0
- package/dist/toast/index.d.ts +27 -0
- package/dist/toast/index.js +36 -0
- package/dist/toggle-group/index.d.ts +17 -0
- package/dist/toggle-group/index.js +26 -0
- package/dist/tokens.css +356 -0
- package/dist/tooltip/Tooltip.d.ts +24 -0
- package/dist/tooltip/Tooltip.js +32 -0
- package/dist/tooltip/index.d.ts +2 -0
- package/dist/tooltip/index.js +1 -0
- package/dist/transfer/index.d.ts +64 -0
- package/dist/transfer/index.js +174 -0
- package/dist/transfer/model.d.ts +12 -0
- package/dist/transfer/model.js +33 -0
- package/dist/tree/index.d.ts +39 -0
- package/dist/tree/index.js +36 -0
- package/dist/tree-select/index.d.ts +59 -0
- package/dist/tree-select/index.js +142 -0
- package/dist/tree-select/model.d.ts +31 -0
- package/dist/tree-select/model.js +135 -0
- package/dist/typewriter/index.d.ts +21 -0
- package/dist/typewriter/index.js +101 -0
- package/dist/typewriter/model.d.ts +7 -0
- package/dist/typewriter/model.js +45 -0
- package/dist/upload/index.d.ts +59 -0
- package/dist/upload/index.js +230 -0
- package/dist/virtual-list/index.d.ts +44 -0
- package/dist/virtual-list/index.js +128 -0
- package/dist/virtual-list/model.d.ts +7 -0
- package/dist/virtual-list/model.js +27 -0
- package/package.json +119 -4
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const markPattern = /^\p{Mark}$/u;
|
|
2
|
+
const punctuationPattern = /^\p{Punctuation}+$/u;
|
|
3
|
+
const regionalIndicatorPattern = /^\p{Regional_Indicator}$/u;
|
|
4
|
+
const emojiModifierPattern = /^\p{Emoji_Modifier}$/u;
|
|
5
|
+
const tagPattern = /^[\u{E0020}-\u{E007F}]$/u;
|
|
6
|
+
const variationSelectorPattern = /^[\uFE00-\uFE0F\u{E0100}-\u{E01EF}]$/u;
|
|
7
|
+
const zeroWidthJoiner = '\u200D';
|
|
8
|
+
function fallbackGraphemes(value) {
|
|
9
|
+
const clusters = [];
|
|
10
|
+
let regionalIndicators = 0;
|
|
11
|
+
for (const character of Array.from(value)) {
|
|
12
|
+
const previous = clusters[clusters.length - 1];
|
|
13
|
+
const joinsPrevious = previous !== undefined && (character === zeroWidthJoiner
|
|
14
|
+
|| previous.endsWith(zeroWidthJoiner)
|
|
15
|
+
|| markPattern.test(character)
|
|
16
|
+
|| emojiModifierPattern.test(character)
|
|
17
|
+
|| variationSelectorPattern.test(character)
|
|
18
|
+
|| tagPattern.test(character)
|
|
19
|
+
|| (regionalIndicatorPattern.test(character) && regionalIndicators % 2 === 1));
|
|
20
|
+
if (joinsPrevious)
|
|
21
|
+
clusters[clusters.length - 1] += character;
|
|
22
|
+
else
|
|
23
|
+
clusters.push(character);
|
|
24
|
+
if (regionalIndicatorPattern.test(character))
|
|
25
|
+
regionalIndicators += 1;
|
|
26
|
+
else
|
|
27
|
+
regionalIndicators = 0;
|
|
28
|
+
}
|
|
29
|
+
return clusters;
|
|
30
|
+
}
|
|
31
|
+
export function splitTypewriterGraphemes(value, locale) {
|
|
32
|
+
const Segmenter = Intl.Segmenter;
|
|
33
|
+
if (!Segmenter)
|
|
34
|
+
return fallbackGraphemes(value);
|
|
35
|
+
return Array.from(new Segmenter(locale, { granularity: 'grapheme' }).segment(value), item => item.segment);
|
|
36
|
+
}
|
|
37
|
+
export function isTypewriterPunctuation(grapheme) {
|
|
38
|
+
return punctuationPattern.test(grapheme);
|
|
39
|
+
}
|
|
40
|
+
function duration(value) {
|
|
41
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
42
|
+
}
|
|
43
|
+
export function typewriterDelayAfter(grapheme, { speed, punctuationDelay }) {
|
|
44
|
+
return duration(speed) + (isTypewriterPunctuation(grapheme) ? duration(punctuationDelay) : 0);
|
|
45
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type HTMLAttributes, type InputHTMLAttributes } from 'react';
|
|
2
|
+
export type UploadStatus = 'selected' | 'uploading' | 'success' | 'error' | 'cancelled';
|
|
3
|
+
export interface UploadItem {
|
|
4
|
+
id: string;
|
|
5
|
+
file: File;
|
|
6
|
+
status: UploadStatus;
|
|
7
|
+
progress?: number;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export type UploadRejectionCode = 'accept' | 'max-size' | 'max-files' | 'custom';
|
|
11
|
+
export interface UploadRejection {
|
|
12
|
+
file: File;
|
|
13
|
+
code: UploadRejectionCode;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
export type UploadChangeReason = 'add' | 'remove' | 'upload-start' | 'upload-progress' | 'upload-success' | 'upload-error' | 'cancel' | 'retry';
|
|
17
|
+
export interface UploadChangeDetails {
|
|
18
|
+
reason: UploadChangeReason;
|
|
19
|
+
item?: UploadItem;
|
|
20
|
+
}
|
|
21
|
+
export interface UploadRequestContext {
|
|
22
|
+
signal: AbortSignal;
|
|
23
|
+
reportProgress: (progress: number) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface UploadLabels {
|
|
26
|
+
browse: string;
|
|
27
|
+
drop: string;
|
|
28
|
+
selected: string;
|
|
29
|
+
upload: string;
|
|
30
|
+
uploading: string;
|
|
31
|
+
success: string;
|
|
32
|
+
error: string;
|
|
33
|
+
cancelled: string;
|
|
34
|
+
retry: string;
|
|
35
|
+
cancel: string;
|
|
36
|
+
remove: string;
|
|
37
|
+
rejected: string;
|
|
38
|
+
acceptError: string;
|
|
39
|
+
sizeError: (maxSize: number) => string;
|
|
40
|
+
countError: (maxFiles: number) => string;
|
|
41
|
+
}
|
|
42
|
+
export interface UploadProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
43
|
+
accept?: string;
|
|
44
|
+
autoUpload?: boolean;
|
|
45
|
+
capture?: InputHTMLAttributes<HTMLInputElement>['capture'];
|
|
46
|
+
defaultFiles?: readonly UploadItem[];
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
files?: readonly UploadItem[];
|
|
49
|
+
inputProps?: Omit<InputHTMLAttributes<HTMLInputElement>, 'accept' | 'capture' | 'disabled' | 'multiple' | 'name' | 'onChange' | 'type'>;
|
|
50
|
+
labels?: Partial<UploadLabels>;
|
|
51
|
+
maxFiles?: number;
|
|
52
|
+
maxSize?: number;
|
|
53
|
+
multiple?: boolean;
|
|
54
|
+
onFilesChange?: (files: readonly UploadItem[], details: UploadChangeDetails) => void;
|
|
55
|
+
onReject?: (rejections: readonly UploadRejection[]) => void;
|
|
56
|
+
onUpload?: (file: File, context: UploadRequestContext) => Promise<void> | void;
|
|
57
|
+
validate?: (file: File, acceptedFiles: readonly UploadItem[]) => string | undefined;
|
|
58
|
+
}
|
|
59
|
+
export declare const Upload: import("react").ForwardRefExoticComponent<UploadProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useId, useRef, useState, } from 'react';
|
|
3
|
+
import { Button } from '../Button.js';
|
|
4
|
+
import { Progress } from '../display/index.js';
|
|
5
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
6
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
7
|
+
import { PencilText } from '../PencilText.js';
|
|
8
|
+
const uploadPaths = [
|
|
9
|
+
{ d: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4' },
|
|
10
|
+
{ d: 'm17 8-5-5-5 5' },
|
|
11
|
+
{ d: 'M12 3v12' },
|
|
12
|
+
];
|
|
13
|
+
const defaultLabels = {
|
|
14
|
+
browse: 'Choose files',
|
|
15
|
+
drop: 'or drop files here',
|
|
16
|
+
selected: 'Selected',
|
|
17
|
+
upload: 'Upload',
|
|
18
|
+
uploading: 'Uploading',
|
|
19
|
+
success: 'Uploaded',
|
|
20
|
+
error: 'Upload failed',
|
|
21
|
+
cancelled: 'Cancelled',
|
|
22
|
+
retry: 'Retry',
|
|
23
|
+
cancel: 'Cancel',
|
|
24
|
+
remove: 'Remove',
|
|
25
|
+
rejected: 'Files not added',
|
|
26
|
+
acceptError: 'This file type is not accepted.',
|
|
27
|
+
sizeError: maxSize => `File exceeds the ${formatBytes(maxSize)} limit.`,
|
|
28
|
+
countError: maxFiles => `Only ${maxFiles} file${maxFiles === 1 ? '' : 's'} may be selected.`,
|
|
29
|
+
};
|
|
30
|
+
function formatBytes(value) {
|
|
31
|
+
if (value < 1024)
|
|
32
|
+
return `${value} B`;
|
|
33
|
+
if (value < 1024 * 1024)
|
|
34
|
+
return `${Math.round(value / 1024)} KB`;
|
|
35
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
36
|
+
}
|
|
37
|
+
function matchesAccept(file, accept) {
|
|
38
|
+
if (!accept?.trim())
|
|
39
|
+
return true;
|
|
40
|
+
const name = file.name.toLowerCase();
|
|
41
|
+
const type = file.type.toLowerCase();
|
|
42
|
+
return accept.split(',').some(value => {
|
|
43
|
+
const rule = value.trim().toLowerCase();
|
|
44
|
+
if (!rule)
|
|
45
|
+
return false;
|
|
46
|
+
if (rule.startsWith('.'))
|
|
47
|
+
return name.endsWith(rule);
|
|
48
|
+
if (rule.endsWith('/*'))
|
|
49
|
+
return type.startsWith(rule.slice(0, -1));
|
|
50
|
+
return type === rule;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function asErrorMessage(error, fallback) {
|
|
54
|
+
return error instanceof Error && error.message ? error.message : fallback;
|
|
55
|
+
}
|
|
56
|
+
function statusLabel(status, labels) {
|
|
57
|
+
return labels[status];
|
|
58
|
+
}
|
|
59
|
+
export const Upload = forwardRef(function Upload({ accept, autoUpload = true, capture, className, defaultFiles = [], disabled = false, files, inputProps, labels: labelOverrides, maxFiles = Number.POSITIVE_INFINITY, maxSize = Number.POSITIVE_INFINITY, multiple = true, onFilesChange, onReject, onUpload, validate, ...props }, ref) {
|
|
60
|
+
const instanceId = useId();
|
|
61
|
+
const inputRef = useRef(null);
|
|
62
|
+
const itemCounter = useRef(0);
|
|
63
|
+
const controllers = useRef(new Map());
|
|
64
|
+
const mounted = useRef(true);
|
|
65
|
+
const disabledRef = useRef(disabled);
|
|
66
|
+
const [internalFiles, setInternalFiles] = useState(defaultFiles);
|
|
67
|
+
const [rejections, setRejections] = useState([]);
|
|
68
|
+
const [dragActive, setDragActive] = useState(false);
|
|
69
|
+
const controlled = files !== undefined;
|
|
70
|
+
const items = files ?? internalFiles;
|
|
71
|
+
const itemsRef = useRef(items);
|
|
72
|
+
itemsRef.current = items;
|
|
73
|
+
disabledRef.current = disabled;
|
|
74
|
+
const labels = { ...defaultLabels, ...labelOverrides };
|
|
75
|
+
const commitRef = useRef(() => { });
|
|
76
|
+
const commit = (next, details) => {
|
|
77
|
+
if (!controlled) {
|
|
78
|
+
itemsRef.current = next;
|
|
79
|
+
setInternalFiles(next);
|
|
80
|
+
}
|
|
81
|
+
onFilesChange?.(next, details);
|
|
82
|
+
};
|
|
83
|
+
commitRef.current = commit;
|
|
84
|
+
const updateItem = (id, update, details) => {
|
|
85
|
+
const current = itemsRef.current;
|
|
86
|
+
const index = current.findIndex(item => item.id === id);
|
|
87
|
+
if (index < 0)
|
|
88
|
+
return;
|
|
89
|
+
const next = [...current];
|
|
90
|
+
next[index] = update(current[index]);
|
|
91
|
+
commitRef.current(next, { ...details, item: next[index] });
|
|
92
|
+
};
|
|
93
|
+
const startUpload = (id, reason = 'upload-start') => {
|
|
94
|
+
const item = itemsRef.current.find(candidate => candidate.id === id);
|
|
95
|
+
if (!item || !onUpload || disabledRef.current)
|
|
96
|
+
return;
|
|
97
|
+
controllers.current.get(id)?.controller.abort();
|
|
98
|
+
const controller = new AbortController();
|
|
99
|
+
const token = Symbol(id);
|
|
100
|
+
controllers.current.set(id, { controller, token });
|
|
101
|
+
updateItem(id, current => ({ ...current, status: 'uploading', progress: 0, error: undefined }), { reason });
|
|
102
|
+
const reportProgress = (progress) => {
|
|
103
|
+
const active = controllers.current.get(id);
|
|
104
|
+
if (!active || active.token !== token || controller.signal.aborted)
|
|
105
|
+
return;
|
|
106
|
+
const normalized = Math.min(100, Math.max(0, Number.isFinite(progress) ? progress : 0));
|
|
107
|
+
updateItem(id, current => ({ ...current, status: 'uploading', progress: normalized }), { reason: 'upload-progress' });
|
|
108
|
+
};
|
|
109
|
+
Promise.resolve().then(() => {
|
|
110
|
+
if (!mounted.current || controller.signal.aborted)
|
|
111
|
+
return undefined;
|
|
112
|
+
return onUpload(item.file, { signal: controller.signal, reportProgress });
|
|
113
|
+
}).then(() => {
|
|
114
|
+
const active = controllers.current.get(id);
|
|
115
|
+
if (!active || active.token !== token || controller.signal.aborted)
|
|
116
|
+
return;
|
|
117
|
+
controllers.current.delete(id);
|
|
118
|
+
updateItem(id, current => ({ ...current, status: 'success', progress: 100, error: undefined }), { reason: 'upload-success' });
|
|
119
|
+
}).catch(error => {
|
|
120
|
+
const active = controllers.current.get(id);
|
|
121
|
+
if (!active || active.token !== token || controller.signal.aborted)
|
|
122
|
+
return;
|
|
123
|
+
controllers.current.delete(id);
|
|
124
|
+
updateItem(id, current => ({ ...current, status: 'error', progress: undefined, error: asErrorMessage(error, labels.error) }), { reason: 'upload-error' });
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
const addFiles = (incoming) => {
|
|
128
|
+
if (disabled || incoming.length === 0)
|
|
129
|
+
return;
|
|
130
|
+
const accepted = [];
|
|
131
|
+
const rejected = [];
|
|
132
|
+
const limit = multiple ? Math.max(0, maxFiles) : Math.min(1, Math.max(0, maxFiles));
|
|
133
|
+
let available = Math.max(0, limit - itemsRef.current.length);
|
|
134
|
+
for (const file of incoming) {
|
|
135
|
+
if (!matchesAccept(file, accept)) {
|
|
136
|
+
rejected.push({ file, code: 'accept', message: labels.acceptError });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (file.size > maxSize) {
|
|
140
|
+
rejected.push({ file, code: 'max-size', message: labels.sizeError(maxSize) });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (available <= 0) {
|
|
144
|
+
rejected.push({ file, code: 'max-files', message: labels.countError(limit) });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const provisional = {
|
|
148
|
+
id: `${instanceId.replace(/:/g, '')}-${itemCounter.current++}`,
|
|
149
|
+
file,
|
|
150
|
+
status: 'selected',
|
|
151
|
+
};
|
|
152
|
+
const customError = validate?.(file, [...itemsRef.current, ...accepted]);
|
|
153
|
+
if (customError) {
|
|
154
|
+
rejected.push({ file, code: 'custom', message: customError });
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
accepted.push(provisional);
|
|
158
|
+
available -= 1;
|
|
159
|
+
}
|
|
160
|
+
setRejections(rejected);
|
|
161
|
+
if (rejected.length)
|
|
162
|
+
onReject?.(rejected);
|
|
163
|
+
if (!accepted.length)
|
|
164
|
+
return;
|
|
165
|
+
commitRef.current([...itemsRef.current, ...accepted], { reason: 'add', item: accepted[accepted.length - 1] });
|
|
166
|
+
if (onUpload && autoUpload)
|
|
167
|
+
accepted.forEach(item => {
|
|
168
|
+
queueMicrotask(() => {
|
|
169
|
+
if (!mounted.current || disabledRef.current)
|
|
170
|
+
return;
|
|
171
|
+
if (itemsRef.current.some(candidate => candidate.id === item.id))
|
|
172
|
+
startUpload(item.id);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
const remove = (id) => {
|
|
177
|
+
if (disabled)
|
|
178
|
+
return;
|
|
179
|
+
controllers.current.get(id)?.controller.abort();
|
|
180
|
+
controllers.current.delete(id);
|
|
181
|
+
const item = itemsRef.current.find(candidate => candidate.id === id);
|
|
182
|
+
commitRef.current(itemsRef.current.filter(candidate => candidate.id !== id), { reason: 'remove', item });
|
|
183
|
+
};
|
|
184
|
+
const cancel = (id) => {
|
|
185
|
+
if (disabled)
|
|
186
|
+
return;
|
|
187
|
+
controllers.current.get(id)?.controller.abort();
|
|
188
|
+
controllers.current.delete(id);
|
|
189
|
+
updateItem(id, item => ({ ...item, status: 'cancelled', progress: undefined, error: undefined }), { reason: 'cancel' });
|
|
190
|
+
};
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
mounted.current = true;
|
|
193
|
+
return () => {
|
|
194
|
+
mounted.current = false;
|
|
195
|
+
controllers.current.forEach(active => active.controller.abort());
|
|
196
|
+
controllers.current.clear();
|
|
197
|
+
};
|
|
198
|
+
}, []);
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
const activeIds = new Set(items.map(item => item.id));
|
|
201
|
+
controllers.current.forEach((active, id) => {
|
|
202
|
+
if (!activeIds.has(id)) {
|
|
203
|
+
active.controller.abort();
|
|
204
|
+
controllers.current.delete(id);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}, [items]);
|
|
208
|
+
const openPicker = () => {
|
|
209
|
+
if (!disabled)
|
|
210
|
+
inputRef.current?.click();
|
|
211
|
+
};
|
|
212
|
+
const handleKeyDown = (event) => {
|
|
213
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
214
|
+
return;
|
|
215
|
+
event.preventDefault();
|
|
216
|
+
openPicker();
|
|
217
|
+
};
|
|
218
|
+
const handleDrop = (event) => {
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
setDragActive(false);
|
|
221
|
+
addFiles(Array.from(event.dataTransfer.files));
|
|
222
|
+
};
|
|
223
|
+
return _jsxs("div", { ...props, ref: ref, className: ['inkra-upload', className].filter(Boolean).join(' '), "data-disabled": disabled || undefined, children: [_jsx("input", { ...inputProps, ref: inputRef, className: ['inkra-upload__input', inputProps?.className].filter(Boolean).join(' '), type: "file", accept: accept, capture: capture, multiple: multiple, disabled: disabled, onChange: event => {
|
|
224
|
+
addFiles(Array.from(event.currentTarget.files ?? []));
|
|
225
|
+
event.currentTarget.value = '';
|
|
226
|
+
} }), _jsxs(PencilSurface, { as: "div", className: "inkra-upload__dropzone", "data-drag-active": dragActive || undefined, variant: dragActive ? 'selection' : 'outline', role: "button", tabIndex: disabled ? -1 : 0, "aria-disabled": disabled || undefined, onClick: openPicker, onKeyDown: handleKeyDown, onDragEnter: event => { event.preventDefault(); if (!disabled)
|
|
227
|
+
setDragActive(true); }, onDragOver: event => { event.preventDefault(); if (!disabled)
|
|
228
|
+
event.dataTransfer.dropEffect = 'copy'; }, onDragLeave: event => { if (!event.currentTarget.contains(event.relatedTarget))
|
|
229
|
+
setDragActive(false); }, onDrop: handleDrop, children: [_jsx(PencilIcon, { className: "inkra-upload__icon", paths: uploadPaths, size: 32, sourceSize: 24 }), _jsxs("span", { className: "inkra-upload__prompt", children: [_jsx(PencilText, { children: labels.browse }), _jsx("span", { children: labels.drop })] }), accept && _jsx("span", { className: "inkra-upload__hint", children: accept })] }), rejections.length > 0 && _jsxs("div", { className: "inkra-upload__rejections", role: "alert", children: [_jsx("strong", { children: labels.rejected }), _jsx("ul", { children: rejections.map((rejection, index) => _jsxs("li", { children: [rejection.file.name, ": ", rejection.message] }, `${rejection.file.name}-${index}`)) })] }), items.length > 0 && _jsx("ul", { className: "inkra-upload__list", "aria-live": "polite", children: items.map(item => _jsx("li", { className: "inkra-upload__item", "data-status": item.status, children: _jsxs(PencilSurface, { className: "inkra-upload__item-surface", variant: "paper", radius: 8, children: [_jsxs("div", { className: "inkra-upload__file", children: [_jsx("span", { className: "inkra-upload__name", children: item.file.name }), _jsxs("span", { className: "inkra-upload__meta", children: [formatBytes(item.file.size), " \u00B7 ", statusLabel(item.status, labels)] }), item.status === 'uploading' && _jsx(Progress, { size: "sm", value: item.progress ?? 0, "aria-label": `${item.file.name}: ${Math.round(item.progress ?? 0)}%` }), item.error && _jsx("span", { className: "inkra-upload__error", children: item.error })] }), _jsxs("div", { className: "inkra-upload__actions", children: [item.status === 'uploading' && _jsx(Button, { disabled: disabled, size: "sm", variant: "ghost", onClick: () => cancel(item.id), children: labels.cancel }), onUpload && item.status === 'selected' && _jsx(Button, { disabled: disabled, size: "sm", variant: "ghost", onClick: () => startUpload(item.id), children: labels.upload }), onUpload && (item.status === 'error' || item.status === 'cancelled') && _jsx(Button, { disabled: disabled, size: "sm", variant: "ghost", onClick: () => startUpload(item.id, 'retry'), children: labels.retry }), _jsx(Button, { disabled: disabled, size: "sm", variant: "ghost", onClick: () => remove(item.id), children: labels.remove })] })] }) }, item.id)) })] });
|
|
230
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type CSSProperties, type HTMLAttributes, type ReactElement, type ReactNode, type RefAttributes } from 'react';
|
|
2
|
+
import { type VirtualListKey } from './model.js';
|
|
3
|
+
export type VirtualListScrollAlignment = 'start' | 'center' | 'end' | 'auto';
|
|
4
|
+
export type VirtualListScrollBehavior = 'auto' | 'smooth' | 'instant';
|
|
5
|
+
export interface VirtualListScrollOptions {
|
|
6
|
+
align?: VirtualListScrollAlignment;
|
|
7
|
+
behavior?: VirtualListScrollBehavior;
|
|
8
|
+
}
|
|
9
|
+
export interface VirtualListItemState<Item> {
|
|
10
|
+
item: Item;
|
|
11
|
+
index: number;
|
|
12
|
+
key: VirtualListKey;
|
|
13
|
+
isScrolling: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type VirtualListItemProps = Omit<HTMLAttributes<HTMLLIElement>, 'children' | 'style'>;
|
|
16
|
+
export interface VirtualListHandle {
|
|
17
|
+
scrollToIndex: (index: number, options?: VirtualListScrollOptions) => void;
|
|
18
|
+
scrollToOffset: (offset: number, options?: VirtualListScrollOptions) => void;
|
|
19
|
+
measure: () => void;
|
|
20
|
+
getScrollElement: () => HTMLElement | null;
|
|
21
|
+
}
|
|
22
|
+
export interface VirtualListProps<Item> extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
23
|
+
items: readonly Item[];
|
|
24
|
+
itemKey: (item: Item, index: number) => VirtualListKey;
|
|
25
|
+
estimateSize: number | ((item: Item, index: number) => number);
|
|
26
|
+
renderItem: (state: VirtualListItemState<Item>) => ReactNode;
|
|
27
|
+
getItemProps?: (state: VirtualListItemState<Item>) => VirtualListItemProps;
|
|
28
|
+
measureItems?: boolean;
|
|
29
|
+
overscan?: number;
|
|
30
|
+
gap?: number;
|
|
31
|
+
paddingStart?: number;
|
|
32
|
+
paddingEnd?: number;
|
|
33
|
+
scrollMargin?: number;
|
|
34
|
+
initialOffset?: number;
|
|
35
|
+
height?: CSSProperties['blockSize'];
|
|
36
|
+
getScrollElement?: () => HTMLElement | null;
|
|
37
|
+
ssrItemCount?: number;
|
|
38
|
+
empty?: ReactNode;
|
|
39
|
+
emptyLabel?: string;
|
|
40
|
+
listLabel?: string;
|
|
41
|
+
listRole?: HTMLAttributes<HTMLUListElement>['role'];
|
|
42
|
+
}
|
|
43
|
+
export declare const VirtualList: <Item>(props: VirtualListProps<Item> & RefAttributes<VirtualListHandle>) => ReactElement | null;
|
|
44
|
+
export type { VirtualListKey } from './model.js';
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
3
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
4
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState, } from 'react';
|
|
5
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
6
|
+
import { PencilLine } from '../PencilLine.js';
|
|
7
|
+
import { clampVirtualListIndex, getVirtualListSsrIndexes, normalizeVirtualListOverscan, normalizeVirtualListSize, normalizeVirtualListSpacing, virtualListKeyAttribute, } from './model.js';
|
|
8
|
+
function classes(...values) {
|
|
9
|
+
return values.filter(Boolean).join(' ');
|
|
10
|
+
}
|
|
11
|
+
function toEngineScrollOptions(options) {
|
|
12
|
+
return options;
|
|
13
|
+
}
|
|
14
|
+
function VirtualListInner({ 'aria-label': ariaLabel, className, empty, emptyLabel = 'No items', estimateSize, gap, getItemProps, getScrollElement, height, initialOffset = 0, itemKey, items, listLabel, listRole = 'list', measureItems = false, onKeyDown, overscan, paddingEnd, paddingStart, renderItem, role: roleProp, scrollMargin, ssrItemCount, style, tabIndex, ...props }, forwardedRef) {
|
|
15
|
+
const rootRef = useRef(null);
|
|
16
|
+
const itemsRef = useRef(items);
|
|
17
|
+
itemsRef.current = items;
|
|
18
|
+
const [hydrated, setHydrated] = useState(false);
|
|
19
|
+
const pendingIndexScrollRef = useRef(null);
|
|
20
|
+
const externalScroll = getScrollElement !== undefined;
|
|
21
|
+
const role = roleProp ?? (ariaLabel ? 'region' : undefined);
|
|
22
|
+
const safeScrollMargin = normalizeVirtualListSpacing(scrollMargin);
|
|
23
|
+
const safeGap = normalizeVirtualListSpacing(gap);
|
|
24
|
+
const safePaddingStart = normalizeVirtualListSpacing(paddingStart);
|
|
25
|
+
const safePaddingEnd = normalizeVirtualListSpacing(paddingEnd);
|
|
26
|
+
useEffect(() => setHydrated(true), []);
|
|
27
|
+
const resolveScrollElement = useCallback(() => getScrollElement?.() ?? rootRef.current, [getScrollElement]);
|
|
28
|
+
const resolveItemKey = useCallback((index) => itemKey(itemsRef.current[index], index), [itemKey]);
|
|
29
|
+
const resolveEstimateSize = useCallback((index) => {
|
|
30
|
+
const item = itemsRef.current[index];
|
|
31
|
+
const value = typeof estimateSize === 'function' ? estimateSize(item, index) : estimateSize;
|
|
32
|
+
return normalizeVirtualListSize(value);
|
|
33
|
+
}, [estimateSize]);
|
|
34
|
+
const virtualizer = useVirtualizer({
|
|
35
|
+
count: items.length,
|
|
36
|
+
enabled: hydrated && items.length > 0,
|
|
37
|
+
estimateSize: resolveEstimateSize,
|
|
38
|
+
gap: safeGap,
|
|
39
|
+
getItemKey: resolveItemKey,
|
|
40
|
+
getScrollElement: resolveScrollElement,
|
|
41
|
+
initialOffset,
|
|
42
|
+
overscan: normalizeVirtualListOverscan(overscan),
|
|
43
|
+
paddingEnd: safePaddingEnd,
|
|
44
|
+
paddingStart: safePaddingStart,
|
|
45
|
+
scrollMargin: safeScrollMargin,
|
|
46
|
+
});
|
|
47
|
+
const virtualRows = virtualizer.getVirtualItems();
|
|
48
|
+
const virtualized = hydrated && virtualRows.length > 0;
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (hydrated)
|
|
51
|
+
virtualizer.measure();
|
|
52
|
+
}, [hydrated, measureItems, virtualizer]);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const pending = pendingIndexScrollRef.current;
|
|
55
|
+
if (!virtualized || !pending)
|
|
56
|
+
return;
|
|
57
|
+
pendingIndexScrollRef.current = null;
|
|
58
|
+
virtualizer.scrollToIndex(pending.index, toEngineScrollOptions(pending.options));
|
|
59
|
+
}, [virtualized, virtualizer]);
|
|
60
|
+
useImperativeHandle(forwardedRef, () => ({
|
|
61
|
+
getScrollElement: resolveScrollElement,
|
|
62
|
+
measure: () => virtualizer.measure(),
|
|
63
|
+
scrollToIndex: (requestedIndex, options) => {
|
|
64
|
+
const index = clampVirtualListIndex(requestedIndex, itemsRef.current.length);
|
|
65
|
+
if (index === null)
|
|
66
|
+
return;
|
|
67
|
+
if (!virtualized) {
|
|
68
|
+
pendingIndexScrollRef.current = { index, options };
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
virtualizer.scrollToIndex(index, toEngineScrollOptions(options));
|
|
72
|
+
},
|
|
73
|
+
scrollToOffset: (offset, options) => {
|
|
74
|
+
if (!Number.isFinite(offset))
|
|
75
|
+
return;
|
|
76
|
+
virtualizer.scrollToOffset(Math.max(0, offset), toEngineScrollOptions(options));
|
|
77
|
+
},
|
|
78
|
+
}), [resolveScrollElement, virtualized, virtualizer]);
|
|
79
|
+
function handleKeyDown(event) {
|
|
80
|
+
onKeyDown?.(event);
|
|
81
|
+
if (event.defaultPrevented || externalScroll || event.target !== event.currentTarget || items.length === 0)
|
|
82
|
+
return;
|
|
83
|
+
if (event.key === 'Home') {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
virtualizer.scrollToIndex(0, { align: 'start' });
|
|
86
|
+
}
|
|
87
|
+
else if (event.key === 'End') {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
virtualizer.scrollToIndex(items.length - 1, { align: 'end' });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const fallbackIndexes = getVirtualListSsrIndexes(items.length, ssrItemCount);
|
|
93
|
+
const renderedRows = virtualized
|
|
94
|
+
? virtualRows.map(row => ({ index: row.index, key: row.key, start: row.start }))
|
|
95
|
+
: fallbackIndexes.map(index => ({ index, key: itemKey(items[index], index), start: null }));
|
|
96
|
+
const rootStyle = externalScroll ? style : { blockSize: height, ...style };
|
|
97
|
+
const content = _jsxs(_Fragment, { children: [_jsx("ul", { "aria-label": listLabel, className: "inkra-virtual-list__items", role: listRole, style: virtualized ? { blockSize: `${virtualizer.getTotalSize()}px` } : undefined, children: renderedRows.map(row => {
|
|
98
|
+
const item = items[row.index];
|
|
99
|
+
const state = {
|
|
100
|
+
item,
|
|
101
|
+
index: row.index,
|
|
102
|
+
key: row.key,
|
|
103
|
+
isScrolling: virtualized && virtualizer.isScrolling,
|
|
104
|
+
};
|
|
105
|
+
const itemProps = getItemProps?.(state) ?? {};
|
|
106
|
+
const { className: itemClassName, role: itemRole, ...nativeItemProps } = itemProps;
|
|
107
|
+
return _createElement("li", { ...nativeItemProps, "aria-posinset": row.index + 1, "aria-setsize": items.length, className: classes('inkra-virtual-list__item', itemClassName), "data-index": row.index, "data-item-key": virtualListKeyAttribute(row.key), key: row.key, ref: virtualized && measureItems ? virtualizer.measureElement : undefined, role: itemRole ?? 'listitem', style: row.start === null ? undefined : {
|
|
108
|
+
transform: `translateY(${row.start - safeScrollMargin}px)`,
|
|
109
|
+
} },
|
|
110
|
+
row.index > 0 && _jsx(PencilLine, { className: "inkra-virtual-list__separator", seed: `virtual-list-separator-${virtualListKeyAttribute(row.key)}`, weight: 0.75 }),
|
|
111
|
+
renderItem(state));
|
|
112
|
+
}) }), items.length === 0 && _jsx("div", { className: "inkra-virtual-list__empty", role: "status", children: empty ?? emptyLabel })] });
|
|
113
|
+
const rootProps = {
|
|
114
|
+
...props,
|
|
115
|
+
'aria-label': ariaLabel,
|
|
116
|
+
className: classes('inkra-virtual-list', className),
|
|
117
|
+
'data-external-scroll': externalScroll || undefined,
|
|
118
|
+
'data-virtualized': virtualized || undefined,
|
|
119
|
+
onKeyDown: handleKeyDown,
|
|
120
|
+
role,
|
|
121
|
+
style: rootStyle,
|
|
122
|
+
tabIndex: externalScroll ? tabIndex : (tabIndex ?? 0),
|
|
123
|
+
};
|
|
124
|
+
return externalScroll
|
|
125
|
+
? _jsx("div", { ...rootProps, ref: rootRef, children: content })
|
|
126
|
+
: _jsx(PencilSurface, { ...rootProps, as: "div", radius: 8, ref: rootRef, seed: "virtual-list", variant: "paper", children: content });
|
|
127
|
+
}
|
|
128
|
+
export const VirtualList = forwardRef(VirtualListInner);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type VirtualListKey = string | number | bigint;
|
|
2
|
+
export declare function normalizeVirtualListSize(value: number, fallback?: number): number;
|
|
3
|
+
export declare function normalizeVirtualListOverscan(value: number | undefined, fallback?: number): number;
|
|
4
|
+
export declare function normalizeVirtualListSpacing(value: number | undefined): number;
|
|
5
|
+
export declare function getVirtualListSsrIndexes(length: number, requested: number | undefined): number[];
|
|
6
|
+
export declare function clampVirtualListIndex(index: number, count: number): number | null;
|
|
7
|
+
export declare function virtualListKeyAttribute(key: VirtualListKey): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function normalizeVirtualListSize(value, fallback = 44) {
|
|
2
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
3
|
+
}
|
|
4
|
+
export function normalizeVirtualListOverscan(value, fallback = 4) {
|
|
5
|
+
if (!Number.isFinite(value))
|
|
6
|
+
return fallback;
|
|
7
|
+
return Math.max(0, Math.floor(value));
|
|
8
|
+
}
|
|
9
|
+
export function normalizeVirtualListSpacing(value) {
|
|
10
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
11
|
+
}
|
|
12
|
+
export function getVirtualListSsrIndexes(length, requested) {
|
|
13
|
+
const safeLength = Math.max(0, Math.floor(length));
|
|
14
|
+
const defaultCount = Math.min(safeLength, 12);
|
|
15
|
+
const count = Number.isFinite(requested)
|
|
16
|
+
? Math.min(safeLength, Math.max(0, Math.floor(requested)))
|
|
17
|
+
: defaultCount;
|
|
18
|
+
return Array.from({ length: count }, (_, index) => index);
|
|
19
|
+
}
|
|
20
|
+
export function clampVirtualListIndex(index, count) {
|
|
21
|
+
if (count <= 0 || !Number.isFinite(index))
|
|
22
|
+
return null;
|
|
23
|
+
return Math.min(count - 1, Math.max(0, Math.floor(index)));
|
|
24
|
+
}
|
|
25
|
+
export function virtualListKeyAttribute(key) {
|
|
26
|
+
return String(key);
|
|
27
|
+
}
|