@baklavue/mcp 1.0.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/README.md +72 -0
- package/dist/data/component-categories.d.ts +7 -0
- package/dist/data/component-categories.js +48 -0
- package/dist/data/loaders.d.ts +22 -0
- package/dist/data/loaders.js +346 -0
- package/dist/docs/components/accordion.md +601 -0
- package/dist/docs/components/alert.md +233 -0
- package/dist/docs/components/badge.md +100 -0
- package/dist/docs/components/banner.md +231 -0
- package/dist/docs/components/button.md +324 -0
- package/dist/docs/components/checkbox.md +343 -0
- package/dist/docs/components/chip.md +199 -0
- package/dist/docs/components/datepicker.md +243 -0
- package/dist/docs/components/dialog.md +224 -0
- package/dist/docs/components/drawer.md +188 -0
- package/dist/docs/components/dropdown.md +291 -0
- package/dist/docs/components/file-upload.md +248 -0
- package/dist/docs/components/icon.md +142 -0
- package/dist/docs/components/image.md +161 -0
- package/dist/docs/components/index.md +151 -0
- package/dist/docs/components/input.md +407 -0
- package/dist/docs/components/link.md +249 -0
- package/dist/docs/components/notification.md +179 -0
- package/dist/docs/components/pagination.md +168 -0
- package/dist/docs/components/radio.md +221 -0
- package/dist/docs/components/scroll-to-top.md +90 -0
- package/dist/docs/components/select.md +239 -0
- package/dist/docs/components/skeleton.md +79 -0
- package/dist/docs/components/spinner.md +93 -0
- package/dist/docs/components/split-button.md +165 -0
- package/dist/docs/components/stepper.md +337 -0
- package/dist/docs/components/switch.md +144 -0
- package/dist/docs/components/tab.md +140 -0
- package/dist/docs/components/table.md +362 -0
- package/dist/docs/components/tag.md +243 -0
- package/dist/docs/components/textarea.md +190 -0
- package/dist/docs/components/tooltip.md +155 -0
- package/dist/docs/composables/alert.md +87 -0
- package/dist/docs/composables/asyncState.md +74 -0
- package/dist/docs/composables/base64.md +72 -0
- package/dist/docs/composables/breakpoints.md +129 -0
- package/dist/docs/composables/clipboard.md +108 -0
- package/dist/docs/composables/colorScheme.md +110 -0
- package/dist/docs/composables/confirmDialog.md +105 -0
- package/dist/docs/composables/containerScroll.md +89 -0
- package/dist/docs/composables/cookie.md +137 -0
- package/dist/docs/composables/debounce.md +69 -0
- package/dist/docs/composables/disclosure.md +69 -0
- package/dist/docs/composables/elementSize.md +84 -0
- package/dist/docs/composables/fetch.md +257 -0
- package/dist/docs/composables/fieldArray.md +104 -0
- package/dist/docs/composables/file.md +343 -0
- package/dist/docs/composables/focusTrap.md +87 -0
- package/dist/docs/composables/formPersistence.md +69 -0
- package/dist/docs/composables/formState.md +71 -0
- package/dist/docs/composables/formValidation.md +355 -0
- package/dist/docs/composables/format.md +107 -0
- package/dist/docs/composables/id.md +54 -0
- package/dist/docs/composables/index.md +112 -0
- package/dist/docs/composables/infiniteQuery.md +104 -0
- package/dist/docs/composables/intersectionObserver.md +64 -0
- package/dist/docs/composables/lazyQuery.md +68 -0
- package/dist/docs/composables/loading.md +91 -0
- package/dist/docs/composables/mutation.md +83 -0
- package/dist/docs/composables/notification.md +169 -0
- package/dist/docs/composables/pagination.md +109 -0
- package/dist/docs/composables/polling.md +76 -0
- package/dist/docs/composables/previous.md +58 -0
- package/dist/docs/composables/query.md +248 -0
- package/dist/docs/composables/raf.md +78 -0
- package/dist/docs/composables/scrollLock.md +46 -0
- package/dist/docs/composables/scrollToError.md +291 -0
- package/dist/docs/composables/scrollVisibility.md +60 -0
- package/dist/docs/composables/share.md +78 -0
- package/dist/docs/composables/slug.md +58 -0
- package/dist/docs/composables/stepper.md +117 -0
- package/dist/docs/composables/stepperForm.md +74 -0
- package/dist/docs/composables/sticky.md +91 -0
- package/dist/docs/composables/storage.md +193 -0
- package/dist/docs/composables/theme.md +252 -0
- package/dist/docs/composables/themePreset.md +62 -0
- package/dist/docs/composables/throttle.md +76 -0
- package/dist/docs/composables/timer.md +78 -0
- package/dist/docs/composables/toggle.md +55 -0
- package/dist/docs/guide/contributing.md +364 -0
- package/dist/docs/guide/design-tokens.md +29 -0
- package/dist/docs/guide/getting-started.md +181 -0
- package/dist/docs/guide/installation.md +287 -0
- package/dist/docs/guide/localization.md +132 -0
- package/dist/docs/guide/mcp.md +141 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +177 -0
- package/package.json +48 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# useFile
|
|
2
|
+
|
|
3
|
+
A composable for file parsing, creating, and downloading. Supports CSV, TSV, JSON, and Excel (.xlsx, .xls) formats with streaming, Zod validation, transforms, and preview. Accepts `File` or `Blob` for `parseFile`, `preview`, and `parseStream`. Uses PapaParse for RFC 4180-compliant CSV/TSV and SheetJS for Excel.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
### Parse String
|
|
8
|
+
|
|
9
|
+
```vue
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { useFile } from "@baklavue/composables";
|
|
12
|
+
|
|
13
|
+
const { parse } = useFile();
|
|
14
|
+
|
|
15
|
+
const result = parse("name,age\nAlice,30\nBob,25", { format: "csv", header: true });
|
|
16
|
+
console.log(result.data); // [{ name: "Alice", age: "30" }, { name: "Bob", age: "25" }]
|
|
17
|
+
</script>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Parse File
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<template>
|
|
24
|
+
<input type="file" accept=".csv,.tsv,.json,.xlsx,.xls" @change="handleFileUpload" />
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { useFile } from "@baklavue/composables";
|
|
29
|
+
|
|
30
|
+
const { parseFile } = useFile();
|
|
31
|
+
|
|
32
|
+
const handleFileUpload = async (event: Event) => {
|
|
33
|
+
const file = (event.target as HTMLInputElement).files?.[0];
|
|
34
|
+
if (file) {
|
|
35
|
+
const result = await parseFile(file, { header: true });
|
|
36
|
+
console.log(result.data);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
</script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Create CSV
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import { useFile } from "@baklavue/composables";
|
|
47
|
+
|
|
48
|
+
const { create } = useFile();
|
|
49
|
+
|
|
50
|
+
const csv = create([
|
|
51
|
+
{ name: "Alice", age: 30 },
|
|
52
|
+
{ name: "Bob", age: 25 },
|
|
53
|
+
]);
|
|
54
|
+
// Result: "name,age\nAlice,30\nBob,25"
|
|
55
|
+
</script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Download CSV
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<template>
|
|
62
|
+
<button @click="exportData">Export CSV</button>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script setup lang="ts">
|
|
66
|
+
import { useFile } from "@baklavue/composables";
|
|
67
|
+
|
|
68
|
+
const { download } = useFile();
|
|
69
|
+
|
|
70
|
+
const data = [
|
|
71
|
+
{ name: "Alice", age: 30 },
|
|
72
|
+
{ name: "Bob", age: 25 },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const exportData = () => {
|
|
76
|
+
download(data, "export.csv");
|
|
77
|
+
};
|
|
78
|
+
</script>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Supported Formats
|
|
82
|
+
|
|
83
|
+
| Format | Parse | Create | Notes |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| CSV | Yes | Yes | RFC 4180, PapaParse |
|
|
86
|
+
| TSV | Yes | Yes | Tab-delimited, PapaParse |
|
|
87
|
+
| JSON | Yes | Yes | Built-in `JSON.parse` / `JSON.stringify` |
|
|
88
|
+
| Excel (.xlsx, .xls) | Yes | Yes | SheetJS; parse via `parseFile` only (binary format) |
|
|
89
|
+
|
|
90
|
+
Format is auto-detected from file extension when using `parseFile` or `preview`. For `Blob` (without filename), format is inferred from `blob.type` when possible; otherwise specify `format` in options.
|
|
91
|
+
|
|
92
|
+
**Limitations:** `parse()` and `parseStream` do not support Excel; use `parseFile()` instead.
|
|
93
|
+
|
|
94
|
+
## Methods
|
|
95
|
+
|
|
96
|
+
### parse
|
|
97
|
+
|
|
98
|
+
Parses a string synchronously. Returns `{ data, errors, meta }` (and `validationError` if Zod schema fails).
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
parse<T>(content: string, options?: FileParseOptions<T>): ParseResult<T> & { validationError?: z.ZodError }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### parseFile
|
|
105
|
+
|
|
106
|
+
Parses a File or Blob asynchronously. Format auto-detected from filename (File) or `blob.type` (Blob) when omitted.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
parseFile<T>(file: File | Blob, options?: FileParseOptions<T>): Promise<ParseResult<T> & { validationError?: z.ZodError }>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### parseStream
|
|
113
|
+
|
|
114
|
+
Chunked parsing for large CSV/TSV files. Use `step` or `chunk` callbacks. Returns `{ abort }` to stop parsing. Does not support Excel; throws if format is xlsx/xls.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
parseStream(file: File | Blob, options: {
|
|
118
|
+
format?: "csv" | "tsv";
|
|
119
|
+
step?: (result: ParseResult<unknown>, parser: Parser) => void;
|
|
120
|
+
chunk?: (result: ParseResult<unknown>, parser: Parser) => void;
|
|
121
|
+
transform?: (row: unknown) => unknown | null;
|
|
122
|
+
complete?: () => void;
|
|
123
|
+
error?: (err: Error) => void;
|
|
124
|
+
// ...header, dynamicTyping, skipEmptyLines
|
|
125
|
+
}): { abort: () => void }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### preview
|
|
129
|
+
|
|
130
|
+
Parses only the first N rows. Useful for quick inspection of large files.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
preview<T>(file: File | Blob, options?: { rows?: number; format?: FileFormat; ... }): Promise<PreviewResult<T>>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### create
|
|
137
|
+
|
|
138
|
+
Creates a string from data. Supports CSV, TSV, JSON, and Excel (returns base64 for Excel).
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
create(data: FileData, options?: FileCreateOptions): string
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### download
|
|
145
|
+
|
|
146
|
+
Creates a string and triggers a browser download. Adds UTF-8 BOM for CSV/TSV (Excel compatibility).
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
download(data: FileData, filename?: string, options?: FileCreateOptions): void
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Options
|
|
153
|
+
|
|
154
|
+
### FileParseOptions
|
|
155
|
+
|
|
156
|
+
| Option | Type | Default | Description |
|
|
157
|
+
| --- | --- | --- | --- |
|
|
158
|
+
| `format` | `'csv' \| 'tsv' \| 'json' \| 'xlsx' \| 'xls'` | `'csv'` | File format |
|
|
159
|
+
| `delimiter` | `string` | auto (CSV) or `\t` (TSV) | Delimiter character |
|
|
160
|
+
| `header` | `boolean` | `false` | First row is header (returns array of objects) |
|
|
161
|
+
| `dynamicTyping` | `boolean` | `false` | Convert numbers and booleans to their types |
|
|
162
|
+
| `skipEmptyLines` | `boolean \| "greedy"` | `false` | Skip empty lines |
|
|
163
|
+
| `schema` | `z.ZodType` | — | Zod schema for validation |
|
|
164
|
+
| `transform` | `(row) => row \| null` | — | Transform each row; return `null` to skip |
|
|
165
|
+
|
|
166
|
+
### FileCreateOptions
|
|
167
|
+
|
|
168
|
+
| Option | Type | Default | Description |
|
|
169
|
+
| --- | --- | --- | --- |
|
|
170
|
+
| `format` | `'csv' \| 'tsv' \| 'json' \| 'xlsx' \| 'xls'` | `'csv'` | Output format |
|
|
171
|
+
| `delimiter` | `string` | `","` or `"\t"` | Delimiter character |
|
|
172
|
+
| `header` | `boolean` | `true` | Include header row |
|
|
173
|
+
| `columns` | `string[]` | — | Column order for array of objects |
|
|
174
|
+
| `escapeFormulae` | `boolean` | `false` | Escape leading `=`, `+`, `-`, `@` to prevent CSV injection |
|
|
175
|
+
|
|
176
|
+
## FileData Types
|
|
177
|
+
|
|
178
|
+
Accepted by `create` and `download`:
|
|
179
|
+
|
|
180
|
+
- **Array of objects:** `Record<string, unknown>[]` — keys become header row
|
|
181
|
+
- **Array of arrays:** `unknown[][]` — no header unless `header: true` with explicit `columns`
|
|
182
|
+
- **Explicit fields + data:** `{ fields: string[]; data: unknown[][] }` — for custom column order
|
|
183
|
+
|
|
184
|
+
## Examples
|
|
185
|
+
|
|
186
|
+
### Multi-Format (JSON, TSV, Excel)
|
|
187
|
+
|
|
188
|
+
```vue
|
|
189
|
+
<script setup lang="ts">
|
|
190
|
+
import { useFile } from "@baklavue/composables";
|
|
191
|
+
|
|
192
|
+
const { parse, parseFile, create, download } = useFile();
|
|
193
|
+
|
|
194
|
+
// Parse JSON
|
|
195
|
+
const jsonResult = parse('[{"a":1},{"a":2}]', { format: "json" });
|
|
196
|
+
|
|
197
|
+
// Parse TSV
|
|
198
|
+
const tsvResult = parse("name\tage\nAlice\t30", { format: "tsv", header: true });
|
|
199
|
+
|
|
200
|
+
// Parse Excel file (use parseFile, not parse)
|
|
201
|
+
const excelResult = await parseFile(file, { format: "xlsx", header: true });
|
|
202
|
+
|
|
203
|
+
// Create and download Excel
|
|
204
|
+
const data = [{ name: "Alice", age: 30 }];
|
|
205
|
+
download(data, "export.xlsx", { format: "xlsx" });
|
|
206
|
+
</script>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### With Blob
|
|
210
|
+
|
|
211
|
+
```vue
|
|
212
|
+
<script setup lang="ts">
|
|
213
|
+
import { useFile } from "@baklavue/composables";
|
|
214
|
+
|
|
215
|
+
const { parseFile } = useFile();
|
|
216
|
+
|
|
217
|
+
// Parse from Blob (e.g. from fetch response)
|
|
218
|
+
const response = await fetch("/api/export.csv");
|
|
219
|
+
const blob = await response.blob();
|
|
220
|
+
const result = await parseFile(blob, { format: "csv", header: true });
|
|
221
|
+
</script>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### With Zod Validation
|
|
225
|
+
|
|
226
|
+
```vue
|
|
227
|
+
<script setup lang="ts">
|
|
228
|
+
import { useFile } from "@baklavue/composables";
|
|
229
|
+
import { z } from "zod";
|
|
230
|
+
|
|
231
|
+
const schema = z.array(z.object({ name: z.string(), age: z.number() }));
|
|
232
|
+
const { parseFile } = useFile();
|
|
233
|
+
|
|
234
|
+
const result = await parseFile(file, {
|
|
235
|
+
format: "csv",
|
|
236
|
+
header: true,
|
|
237
|
+
dynamicTyping: true,
|
|
238
|
+
schema,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (result.validationError) {
|
|
242
|
+
console.error("Validation failed:", result.validationError);
|
|
243
|
+
} else {
|
|
244
|
+
console.log(result.data); // Typed as { name: string; age: number }[]
|
|
245
|
+
}
|
|
246
|
+
</script>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### With Transform
|
|
250
|
+
|
|
251
|
+
```vue
|
|
252
|
+
<script setup lang="ts">
|
|
253
|
+
import { useFile } from "@baklavue/composables";
|
|
254
|
+
|
|
255
|
+
const { parse } = useFile();
|
|
256
|
+
|
|
257
|
+
const result = parse("name,age\nAlice,30\nBob,25", {
|
|
258
|
+
format: "csv",
|
|
259
|
+
header: true,
|
|
260
|
+
dynamicTyping: true,
|
|
261
|
+
transform: (row) => {
|
|
262
|
+
const r = row as { age: number };
|
|
263
|
+
return r.age >= 18 ? { ...r, adult: true } : null;
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
// Only rows with age >= 18, with adult: true added
|
|
267
|
+
</script>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Preview First Rows
|
|
271
|
+
|
|
272
|
+
```vue
|
|
273
|
+
<script setup lang="ts">
|
|
274
|
+
import { useFile } from "@baklavue/composables";
|
|
275
|
+
|
|
276
|
+
const { preview } = useFile();
|
|
277
|
+
|
|
278
|
+
const { data, truncated } = await preview(file, { rows: 10 });
|
|
279
|
+
console.log("First 10 rows:", data);
|
|
280
|
+
if (truncated) console.log("File has more rows");
|
|
281
|
+
</script>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Streaming Large Files
|
|
285
|
+
|
|
286
|
+
```vue
|
|
287
|
+
<script setup lang="ts">
|
|
288
|
+
import { useFile } from "@baklavue/composables";
|
|
289
|
+
|
|
290
|
+
const { parseStream } = useFile();
|
|
291
|
+
|
|
292
|
+
const { abort } = parseStream(file, {
|
|
293
|
+
format: "csv",
|
|
294
|
+
header: true,
|
|
295
|
+
step: (result, parser) => {
|
|
296
|
+
const row = result.data?.[0];
|
|
297
|
+
if (row) processRow(row);
|
|
298
|
+
},
|
|
299
|
+
complete: () => console.log("Done"),
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Call abort() to stop parsing
|
|
303
|
+
</script>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### With Formulae Escaping
|
|
307
|
+
|
|
308
|
+
```vue
|
|
309
|
+
<script setup lang="ts">
|
|
310
|
+
import { useFile } from "@baklavue/composables";
|
|
311
|
+
|
|
312
|
+
const { download } = useFile();
|
|
313
|
+
|
|
314
|
+
download(
|
|
315
|
+
[{ formula: "=SUM(A1:A10)", value: 100 }],
|
|
316
|
+
"export.csv",
|
|
317
|
+
{ escapeFormulae: true }
|
|
318
|
+
);
|
|
319
|
+
</script>
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## TypeScript Support
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import {
|
|
326
|
+
useFile,
|
|
327
|
+
type FileData,
|
|
328
|
+
type FileParseOptions,
|
|
329
|
+
type FileCreateOptions,
|
|
330
|
+
type ParseResult,
|
|
331
|
+
type ParseError,
|
|
332
|
+
type ParseMeta,
|
|
333
|
+
type PreviewResult,
|
|
334
|
+
} from "@baklavue/composables";
|
|
335
|
+
|
|
336
|
+
const { parse, parseFile, create, download } = useFile();
|
|
337
|
+
|
|
338
|
+
// Typed parse
|
|
339
|
+
const result = parse<{ name: string; age: number }>(
|
|
340
|
+
"name,age\nAlice,30",
|
|
341
|
+
{ format: "csv", header: true, dynamicTyping: true }
|
|
342
|
+
);
|
|
343
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# useFocusTrap
|
|
2
|
+
|
|
3
|
+
A composable for trapping focus within a container (e.g. modals, dialogs). Tab cycles to next focusable element; Shift+Tab to previous. Prevents focus from escaping.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```vue
|
|
8
|
+
<template>
|
|
9
|
+
<BvDialog v-model:open="isOpen" caption="Dialog">
|
|
10
|
+
<div ref="dialogContentRef">
|
|
11
|
+
<p>Tab cycles within this dialog.</p>
|
|
12
|
+
<BvButton @click="close">Cancel</BvButton>
|
|
13
|
+
<BvButton variant="primary">Confirm</BvButton>
|
|
14
|
+
</div>
|
|
15
|
+
</BvDialog>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup>
|
|
19
|
+
import { ref, watch } from "vue";
|
|
20
|
+
import { BvButton, BvDialog } from "@baklavue/ui";
|
|
21
|
+
import { useFocusTrap } from "@baklavue/composables";
|
|
22
|
+
|
|
23
|
+
const dialogContentRef = ref(null);
|
|
24
|
+
const isOpen = ref(false);
|
|
25
|
+
|
|
26
|
+
const { activate, deactivate } = useFocusTrap({
|
|
27
|
+
target: dialogContentRef,
|
|
28
|
+
active: isOpen,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
watch(isOpen, (open) => {
|
|
32
|
+
if (open) activate();
|
|
33
|
+
else deactivate();
|
|
34
|
+
});
|
|
35
|
+
</script>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Manual Activation
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<script setup>
|
|
42
|
+
import { ref } from "vue";
|
|
43
|
+
import { useFocusTrap } from "@baklavue/composables";
|
|
44
|
+
|
|
45
|
+
const containerRef = ref(null);
|
|
46
|
+
const { activate, deactivate, focusFirst, focusLast } = useFocusTrap({
|
|
47
|
+
target: containerRef,
|
|
48
|
+
initialFocus: true,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
onMounted(() => {
|
|
52
|
+
activate();
|
|
53
|
+
});
|
|
54
|
+
</script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### Return Value
|
|
60
|
+
|
|
61
|
+
| Property | Type | Description |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| `activate` | `() => void` | Start trapping focus |
|
|
64
|
+
| `deactivate` | `() => void` | Stop trapping focus |
|
|
65
|
+
| `focusFirst` | `() => void` | Focus first focusable element |
|
|
66
|
+
| `focusLast` | `() => void` | Focus last focusable element |
|
|
67
|
+
|
|
68
|
+
### Options
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface UseFocusTrapOptions {
|
|
72
|
+
target: Ref<HTMLElement | null>; // Container element
|
|
73
|
+
active?: Ref<boolean> | boolean; // Whether trap is active (default: true)
|
|
74
|
+
initialFocus?: boolean; // Focus first element when activated (default: true)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## TypeScript Support
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { useFocusTrap, type UseFocusTrapOptions } from "@baklavue/composables";
|
|
82
|
+
|
|
83
|
+
const { activate } = useFocusTrap({
|
|
84
|
+
target: dialogRef,
|
|
85
|
+
active: isOpen,
|
|
86
|
+
});
|
|
87
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# useFormPersistence
|
|
2
|
+
|
|
3
|
+
A composable for auto-saving form data to `localStorage` or `sessionStorage`. Useful for long forms, drafts, or preventing data loss on accidental navigation.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```vue
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { useFormPersistence } from "@baklavue/composables";
|
|
10
|
+
import { ref } from "vue";
|
|
11
|
+
|
|
12
|
+
const form = ref({ email: "", message: "" });
|
|
13
|
+
useFormPersistence("contact-draft", form);
|
|
14
|
+
</script>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Form data is automatically saved when the user types. On page load, if a draft exists in storage, it is restored.
|
|
18
|
+
|
|
19
|
+
## API
|
|
20
|
+
|
|
21
|
+
### Parameters
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
useFormPersistence(key, data, options?)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Parameter | Type | Description |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `key` | `string` | Storage key |
|
|
30
|
+
| `data` | `Ref<T> \| Reactive<T> \| () => T` | Form data (ref, reactive, or getter) |
|
|
31
|
+
| `options` | `UseFormPersistenceOptions` | Optional: `{ storage?: 'localStorage' \| 'sessionStorage', debounce?: number }` |
|
|
32
|
+
|
|
33
|
+
### Return Value
|
|
34
|
+
|
|
35
|
+
| Property | Type | Description |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| `clear` | `() => void` | Remove persisted data from storage |
|
|
38
|
+
|
|
39
|
+
### Options
|
|
40
|
+
|
|
41
|
+
| Option | Type | Default | Description |
|
|
42
|
+
| --- | --- | --- | --- |
|
|
43
|
+
| `storage` | `'localStorage' \| 'sessionStorage'` | `'localStorage'` | Where to persist |
|
|
44
|
+
| `debounce` | `number` | `300` | Debounce writes in ms. Use `0` for immediate write |
|
|
45
|
+
|
|
46
|
+
## Session vs Local Storage
|
|
47
|
+
|
|
48
|
+
- **localStorage**: Persists across browser sessions. Use for drafts the user might return to later.
|
|
49
|
+
- **sessionStorage**: Persists only for the current tab. Use for forms that should not persist after closing.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
useFormPersistence("wizard-draft", form, {
|
|
53
|
+
storage: "sessionStorage",
|
|
54
|
+
debounce: 500,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Clearing Draft
|
|
59
|
+
|
|
60
|
+
Call `clear()` when the form is successfully submitted:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const { clear } = useFormPersistence("contact-draft", form);
|
|
64
|
+
|
|
65
|
+
const onSubmit = async () => {
|
|
66
|
+
await api.submit(form.value);
|
|
67
|
+
clear();
|
|
68
|
+
};
|
|
69
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# useFormState
|
|
2
|
+
|
|
3
|
+
A lightweight composable for form dirty and touched state without validation. Use with plain ref form data or alongside [useZodForm](/composables/formValidation) when you need state tracking without Zod.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```vue
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { useFormState } from "@baklavue/composables";
|
|
10
|
+
import { ref } from "vue";
|
|
11
|
+
|
|
12
|
+
const form = ref({ email: "", name: "" });
|
|
13
|
+
const { isDirty, dirtyFields, touched, touchedFields, setFieldTouched, setFieldValue, reset } = useFormState(form);
|
|
14
|
+
|
|
15
|
+
// Check before navigation
|
|
16
|
+
const beforeUnload = () => {
|
|
17
|
+
if (isDirty.value) {
|
|
18
|
+
return "You have unsaved changes";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
</script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## API
|
|
25
|
+
|
|
26
|
+
### Parameters
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
useFormState(data, options?)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| Parameter | Type | Description |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `data` | `Ref<T> \| Reactive<T> \| () => T` | Form data (ref, reactive, or getter) |
|
|
35
|
+
| `options` | `UseFormStateOptions` | Optional: `{ initialValues?: Ref \| () => T }` |
|
|
36
|
+
|
|
37
|
+
### Return Value
|
|
38
|
+
|
|
39
|
+
| Property | Type | Description |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `isDirty` | `Ref<boolean>` | Whether any field has changed from initial values |
|
|
42
|
+
| `dirtyFields` | `Ref<Record<string, boolean>>` | Per-field dirty state |
|
|
43
|
+
| `touched` | `Ref<boolean>` | Whether any field has been blurred |
|
|
44
|
+
| `touchedFields` | `Ref<Record<string, boolean>>` | Per-field touched state |
|
|
45
|
+
| `setFieldTouched` | `(path: string, value?: boolean) => void` | Mark a field as touched |
|
|
46
|
+
| `setFieldValue` | `(path: string, value: unknown) => void` | Programmatically set a field value |
|
|
47
|
+
| `reset` | `(initialValues?: unknown) => void` | Reset touched state and optionally form data |
|
|
48
|
+
| `initialValues` | `Ref<unknown>` | Snapshot of initial values |
|
|
49
|
+
|
|
50
|
+
## Unsaved Changes Warning
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const { isDirty } = useFormState(form);
|
|
54
|
+
|
|
55
|
+
onBeforeRouteLeave((_to, _from, next) => {
|
|
56
|
+
if (isDirty.value) {
|
|
57
|
+
showConfirmDialog({
|
|
58
|
+
title: "Unsaved changes",
|
|
59
|
+
message: "Leave without saving?",
|
|
60
|
+
onConfirm: () => next(),
|
|
61
|
+
onCancel: () => next(false),
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
next();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## With useZodForm
|
|
70
|
+
|
|
71
|
+
`useZodForm` already provides `isDirty`, `dirtyFields`, `touched`, and `touchedFields`. Use `useFormState` when you have a form without Zod validation but still need dirty/touched tracking.
|