@dmitryvim/form-builder 0.2.27 → 0.2.29

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.
@@ -1,25 +1,61 @@
1
+ import type { FileAccept } from "../../types/schema.js";
1
2
  export interface FileUploadConstraints {
2
3
  maxCount: number;
3
4
  allowedExtensions: string[];
5
+ allowedMimes: string[];
4
6
  maxSize: number;
5
7
  }
6
8
  /**
7
9
  * Extract allowed file extensions from the `accept` schema property.
8
10
  * Returns lowercase extension strings without leading dots.
11
+ * Convention: this getter normalizes to lowercase; checkers assume the list is normalized.
9
12
  */
10
- export declare function getAllowedExtensions(accept: string | {
11
- extensions: string[];
12
- } | undefined): string[];
13
+ export declare function getAllowedExtensions(accept: FileAccept | undefined): string[];
13
14
  /**
14
15
  * Return true if fileName's extension is in the allowed list,
15
16
  * or if the list is empty (no restriction).
16
17
  */
17
18
  export declare function isFileExtensionAllowed(fileName: string, allowedExtensions: string[]): boolean;
19
+ /**
20
+ * Return true if a byte count is within the size limit.
21
+ * maxSizeMB === Infinity means no limit.
22
+ */
23
+ export declare function isSizeWithinLimit(bytes: number, maxSizeMB: number): boolean;
18
24
  /**
19
25
  * Return true if file is within the size limit.
20
26
  * maxSizeMB === Infinity means no limit.
21
27
  */
22
28
  export declare function isFileSizeAllowed(file: File, maxSizeMB: number): boolean;
29
+ /**
30
+ * Extract allowed MIME types from the `accept` schema property.
31
+ * Returns empty array for the legacy string form (no mime in that form).
32
+ * Returns empty array when accept is undefined or has no mime key.
33
+ * Convention: this getter normalizes to lowercase; checkers assume the list is normalized.
34
+ */
35
+ export declare function getAllowedMimes(accept: FileAccept | undefined): string[];
36
+ /**
37
+ * Return true if mimeType is allowed by the allowedMimes list.
38
+ * Empty allowedMimes = no mime restriction → always return true.
39
+ * Supports wildcards (image/*, application/*, etc.).
40
+ * Comparison is case-insensitive per RFC 2045.
41
+ *
42
+ * Convention: allowedMimes is assumed to be pre-normalized to lowercase
43
+ * (by getAllowedMimes). Only mimeType (the host-provided input) is lowercased here.
44
+ */
45
+ export declare function isMimeAllowed(mimeType: string, allowedMimes: string[]): boolean;
46
+ /**
47
+ * Infer MIME type from a resource ID's filename extension.
48
+ * Returns "application/octet-stream" when the extension is unrecognised.
49
+ * This is the single source of truth for the extension→mime mapping used by
50
+ * prefill, handleInitialFileData, and the API-update path.
51
+ */
52
+ export declare function inferMimeFromResourceId(resourceId: string): string;
53
+ /**
54
+ * Seed resourceIndex with inferred metadata for a single resource ID.
55
+ * Skips IDs that already have an entry (does not overwrite authoritative data).
56
+ * Sets inferredFromExtension: true so that mime validation is skipped at submit time.
57
+ */
58
+ export declare function seedInferredResource(resourceId: string, resourceIndex: Map<string, any>): void;
23
59
  /**
24
60
  * Add prefill resource IDs to resourceIndex with type inferred from filename extension.
25
61
  */
@@ -40,5 +40,7 @@ export declare function ensureTilesWrap(list: HTMLElement): HTMLElement;
40
40
  export declare function setEmptyFileContainer(fileContainer: HTMLElement, state: State, hint?: string): void;
41
41
  /**
42
42
  * Set up drag-and-drop on an element, calling dropHandler with the dropped FileList.
43
+ * Idempotent: if called again on the same element, the previous listener triple
44
+ * is removed first so handlers never stack.
43
45
  */
44
46
  export declare function setupDragAndDrop(element: HTMLElement, dropHandler: (files: FileList) => void): void;
@@ -0,0 +1,49 @@
1
+ import type { FormBuilderInstance } from "../../instance/FormBuilderInstance.js";
2
+ import type { State } from "../../types/state.js";
3
+ import type { FileElement, FilesElement } from "../../types/index.js";
4
+ /**
5
+ * Handle a library pick action for a MULTI-file field.
6
+ *
7
+ * End-to-end handler (mirrors upload.ts pattern):
8
+ * 1. Calls pickExistingFiles with context
9
+ * 2. Validates, deduplicates, enforces slot limit
10
+ * 3. Registers in resourceIndex, mutates resourceIds array
11
+ * 4. Updates data-resource-ids attribute
12
+ * 5. Calls updateCallback() to re-render tiles
13
+ * 6. Fires triggerOnChange
14
+ *
15
+ * @param state Form builder state
16
+ * @param element File / FilesElement schema definition
17
+ * @param wrapper The [data-files-wrapper] element
18
+ * @param fieldPath Bracket-notation field path (e.g. "slides[2].image")
19
+ * @param resourceIds The mutable live array of current resource IDs (mutated in-place)
20
+ * @param maxCount Maximum files allowed (Infinity = no limit)
21
+ * @param updateCallback Re-render callback (same pattern as upload.ts)
22
+ * @param instance FormBuilderInstance (for onChange events)
23
+ */
24
+ export declare function handleLibraryPickMulti(state: State, element: FileElement | FilesElement, wrapper: HTMLElement, fieldPath: string, resourceIds: string[], maxCount: number, updateCallback: () => void, instance: FormBuilderInstance): Promise<void>;
25
+ /**
26
+ * Handle a library pick action for a SINGLE-file field.
27
+ *
28
+ * End-to-end handler (mirrors upload.ts and handleLibraryPickMulti patterns):
29
+ * 1. Calls pickExistingFiles with context
30
+ * 2. Validates the first returned resource
31
+ * 3. Registers in resourceIndex
32
+ * 4. Creates/updates the hidden input inside fileWrapper (only on success — not on cancel)
33
+ * 5. Calls renderCallback(resourceId) to render the tile
34
+ * 6. Fires triggerOnChange
35
+ *
36
+ * The hidden input is managed here (not in the caller) to mirror how
37
+ * handleFileSelect in upload.ts manages it, and to match the multi-file
38
+ * pattern where data-resource-ids is updated inside handleLibraryPickMulti.
39
+ *
40
+ * @param state Form builder state
41
+ * @param element FileElement schema definition
42
+ * @param container The .file-preview-container element
43
+ * @param fileWrapper The .space-y-2 wrapper that contains the hidden input
44
+ * @param pathKey Field name / bracket-notation path for the hidden input
45
+ * @param fieldPath Bracket-notation field path (for onChange context)
46
+ * @param renderCallback Called with the accepted resource ID; caller renders the tile
47
+ * @param instance FormBuilderInstance (for onChange events)
48
+ */
49
+ export declare function handleLibraryPickSingle(state: State, element: FileElement, container: HTMLElement, fileWrapper: HTMLElement, pathKey: string, fieldPath: string, renderCallback: (resourceId: string) => Promise<void>, instance: FormBuilderInstance): Promise<void>;
@@ -1,15 +1,18 @@
1
1
  import type { State } from "../../types/state.js";
2
2
  import type { FileElement, FilesElement, RenderContext } from "../../types/index.js";
3
3
  export { TILE_SIZE } from "./dom.js";
4
- /**
5
- * Populate `container` with the tile grid for multi-file fields.
6
- * In edit mode: renders tiles + hover-X removal + "+" add tile.
7
- * In readonly mode (isReadonly=true): renders tiles only (no add tile, no X).
8
- *
9
- * Also writes `data-resource-ids` JSON on the nearest [data-files-wrapper]
10
- * ancestor so that validateFileElement can read IDs without touching pills.
11
- */
12
- export declare function renderResourcePills(container: HTMLElement, rids: string[] | null, state: State, onRemove: ((rid: string) => void) | null, hint?: string, countInfo?: string, maxCount?: number, isReadonly?: boolean): void;
4
+ export interface RenderPillsOptions {
5
+ container: HTMLElement;
6
+ rids: string[] | null;
7
+ state: State;
8
+ onRemove: ((rid: string) => void) | null;
9
+ hint?: string;
10
+ countInfo?: string;
11
+ maxCount?: number;
12
+ isReadonly?: boolean;
13
+ onLibraryPick?: (() => void) | null;
14
+ }
15
+ export declare function renderResourcePills(opts: RenderPillsOptions): void;
13
16
  export declare function renderFileElementEdit(element: FileElement, ctx: RenderContext, wrapper: HTMLElement, pathKey: string): void;
14
17
  export declare function renderFilesElementEdit(element: FilesElement, ctx: RenderContext, wrapper: HTMLElement, pathKey: string): void;
15
18
  export declare function renderMultipleFileElementEdit(element: FileElement, ctx: RenderContext, wrapper: HTMLElement, pathKey: string): void;
@@ -1,4 +1,5 @@
1
1
  import type { State } from "../../types/state.js";
2
+ import type { FormBuilderInstance } from "../../instance/FormBuilderInstance.js";
2
3
  import { type FileUploadConstraints } from "./constraints.js";
3
4
  export interface FileDeps {
4
5
  picker: HTMLInputElement;
@@ -8,6 +9,17 @@ export interface FileDeps {
8
9
  /** Called by the tile's remove button in single-file edit mode */
9
10
  onRemove?: () => void;
10
11
  }
11
- export declare function handleFileSelect(file: File, container: HTMLElement, fieldName: string, state: State, deps?: FileDeps | null, instance?: any, allowedExtensions?: string[], maxSizeMB?: number): Promise<void>;
12
- export declare function setupFilesDropHandler(filesContainer: HTMLElement, resourceIds: string[], state: State, updateCallback: () => void, constraints: FileUploadConstraints, pathKey?: string, instance?: any): void;
13
- export declare function setupFilesPickerHandler(filesPicker: HTMLInputElement, resourceIds: string[], state: State, updateCallback: () => void, constraints: FileUploadConstraints, pathKey?: string, instance?: any): void;
12
+ export interface HandleFileSelectOptions {
13
+ file: File;
14
+ container: HTMLElement;
15
+ fieldName: string;
16
+ state: State;
17
+ deps?: FileDeps | null;
18
+ instance?: FormBuilderInstance | null;
19
+ allowedExtensions?: string[];
20
+ allowedMimes?: string[];
21
+ maxSizeMB?: number;
22
+ }
23
+ export declare function handleFileSelect(opts: HandleFileSelectOptions): Promise<void>;
24
+ export declare function setupFilesDropHandler(filesContainer: HTMLElement, resourceIds: string[], state: State, updateCallback: () => void, constraints: FileUploadConstraints, pathKey?: string, instance?: FormBuilderInstance | null): void;
25
+ export declare function setupFilesPickerHandler(filesPicker: HTMLInputElement, resourceIds: string[], state: State, updateCallback: () => void, constraints: FileUploadConstraints, pathKey?: string, instance?: FormBuilderInstance | null): void;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { FormBuilderInstance } from "./instance/FormBuilderInstance.js";
7
7
  import { validateSchema } from "./utils/validation.js";
8
- export type { SelectOption, ElementAction, BaseElement, TextElement, TextareaElement, NumberElement, SelectElement, FileElement, FilesElement, ContainerElement, GroupElement, RichInputElement, Element, Schema, ExternalAction, FormData, RenderContext, Translations, Locale, Config, ResourceMetadata, State, } from "./types/index.js";
8
+ export type { SelectOption, ElementAction, BaseElement, TextElement, TextareaElement, NumberElement, SelectElement, FileElement, FilesElement, ContainerElement, GroupElement, RichInputElement, Element, Schema, ExternalAction, FormData, RenderContext, Translations, Locale, Config, ResourceMetadata, PickedResource, State, } from "./types/index.js";
9
9
  export type { Theme } from "./styles/theme.js";
10
10
  export { defaultTheme, exampleThemes } from "./styles/theme.js";
11
11
  /**
@@ -1,5 +1,15 @@
1
1
  import type { Theme } from "../styles/theme.js";
2
2
  import type { TableMerge } from "./schema.js";
3
+ /**
4
+ * A file resource returned by the host's library picker.
5
+ * The host validates/provides these fields; the library re-validates on resolve.
6
+ */
7
+ export interface PickedResource {
8
+ resourceId: string;
9
+ name: string;
10
+ type: string;
11
+ size: number;
12
+ }
3
13
  export interface Translations {
4
14
  removeElement: string;
5
15
  clickDragText: string;
@@ -31,6 +41,10 @@ export interface Translations {
31
41
  fileCountRange: string;
32
42
  uploadingFile: string;
33
43
  filesCounter: string;
44
+ fromLibrary: string;
45
+ libraryEmpty: string;
46
+ libraryHint: string;
47
+ pickerError: string;
34
48
  required: string;
35
49
  minItems: string;
36
50
  maxItems: string;
@@ -45,6 +59,7 @@ export interface Translations {
45
59
  minFiles: string;
46
60
  maxFiles: string;
47
61
  invalidFileExtension: string;
62
+ invalidFileMime: string;
48
63
  fileTooLarge: string;
49
64
  filesLimitExceeded: string;
50
65
  unsupportedFieldType: string;
@@ -90,6 +105,17 @@ export interface Config {
90
105
  readonly: boolean;
91
106
  locale: Locale;
92
107
  translations: Record<string, Partial<Translations>>;
108
+ pickExistingFiles: ((context: {
109
+ fieldPath: string;
110
+ mode: "single" | "multiple";
111
+ accept?: {
112
+ extensions?: string[];
113
+ mime?: string[];
114
+ };
115
+ maxSizeMB?: number;
116
+ remainingSlots?: number;
117
+ selectedResourceIds?: string[];
118
+ }) => Promise<PickedResource[]>) | null;
93
119
  parseTableFile: ((file: File) => Promise<{
94
120
  cells: string[][];
95
121
  merges?: TableMerge[];
@@ -102,4 +128,11 @@ export interface ResourceMetadata {
102
128
  size: number;
103
129
  uploadedAt: Date;
104
130
  file?: File;
131
+ /**
132
+ * True when `type` was inferred from the filename extension rather than
133
+ * provided authoritatively by the host (e.g. via uploadFile or pickExistingFiles).
134
+ * When true, mime validation is skipped at submit time because we cannot
135
+ * validate against fabricated data — extension validation still applies.
136
+ */
137
+ inferredFromExtension?: boolean;
105
138
  }
@@ -1,4 +1,4 @@
1
1
  export type { SelectOption, ElementAction, EnableCondition, BaseElement, TextElement, TextareaElement, NumberElement, SelectElement, SwitcherElement, FileElement, FilesElement, ColourElement, SliderElement, ContainerElement, GroupElement, TableElement, TableMerge, TableData, RichInputElement, Element, Schema, ExternalAction, FormData, RenderContext, } from "./schema.js";
2
- export type { Translations, Locale, Config, ResourceMetadata, } from "./config.js";
2
+ export type { Translations, Locale, Config, ResourceMetadata, PickedResource, } from "./config.js";
3
3
  export type { State } from "./state.js";
4
4
  export type { ComponentContext, ValidationResult, ComponentValidator, ComponentUpdater, ComponentOperations, } from "./component-operations.js";
@@ -76,24 +76,31 @@ export interface SelectElement extends BaseElement {
76
76
  minCount?: number;
77
77
  maxCount?: number;
78
78
  }
79
+ /**
80
+ * File accept constraint — either a legacy comma-separated string (e.g. ".jpg,.png")
81
+ * or a structured object with extension list, mime list, or both.
82
+ * `undefined` on the schema field is handled by the optional `?` marker.
83
+ */
84
+ export type FileAccept = string | {
85
+ extensions?: string[];
86
+ mime?: string[];
87
+ };
79
88
  export interface FileElement extends BaseElement {
80
89
  type: "file";
81
- accept?: string | {
82
- extensions: string[];
83
- };
90
+ accept?: FileAccept;
84
91
  maxSize?: number;
85
92
  multiple?: boolean;
86
93
  minCount?: number;
87
94
  maxCount?: number;
95
+ disableLibrary?: boolean;
88
96
  }
89
97
  export interface FilesElement extends BaseElement {
90
98
  type: "files";
91
- accept?: string | {
92
- extensions: string[];
93
- };
99
+ accept?: FileAccept;
94
100
  maxSize?: number;
95
101
  minCount?: number;
96
102
  maxCount?: number;
103
+ disableLibrary?: boolean;
97
104
  }
98
105
  export interface ContainerElement extends BaseElement {
99
106
  type: "container";
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.2.27",
6
+ "version": "0.2.29",
7
7
  "description": "A reusable JSON schema form builder library",
8
8
  "main": "./dist/cjs/index.cjs",
9
9
  "module": "./dist/esm/index.js",