@directus/composables 11.2.0 → 11.2.2

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/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { AppCollection, Field, Item, Query, RefRecord } from '@directus/types';
1
+ import { AppCollection, Field, Item, Query, LayoutConfig, RefRecord, AppExtensionConfigs } from '@directus/types';
2
2
  import { ComputedRef, Ref, WritableComputedRef, Component } from 'vue';
3
- import { AppExtensionConfigs } from '@directus/extensions';
4
3
  import { DirectusClient, RestClient } from '@directus/sdk';
5
4
  import { AxiosInstance } from 'axios';
6
5
 
@@ -14,12 +13,82 @@ type UsableCollection = {
14
13
  isSingleton: ComputedRef<boolean>;
15
14
  accountabilityScope: ComputedRef<'all' | 'activity' | null>;
16
15
  };
16
+ /**
17
+ * A Vue composable that provides reactive access to collection metadata, fields, and computed properties.
18
+ *
19
+ * This composable serves as a centralized way to access and work with Directus collections,
20
+ * providing reactive computed properties for collection information, field definitions,
21
+ * default values, and various collection-specific metadata.
22
+ *
23
+ * @param collectionKey - The collection identifier. Can be a string or a reactive reference to a string.
24
+ * If null, most computed properties will return empty/null values.
25
+ *
26
+ * @returns An object containing reactive computed properties for the collection:
27
+ * - `info` - The complete collection configuration object or null if not found
28
+ * - `fields` - Array of sorted field definitions for the collection
29
+ * - `defaults` - Object mapping field names to their default values from schema
30
+ * - `primaryKeyField` - The field marked as primary key, or null if none exists
31
+ * - `userCreatedField` - The field with 'user_created' special type, or null if none exists
32
+ * - `sortField` - The field name used for sorting, from collection meta, or null
33
+ * - `isSingleton` - Boolean indicating if the collection is configured as a singleton
34
+ * - `accountabilityScope` - The accountability scope setting ('all', 'activity', or null)
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * // Using with a static collection name
39
+ * const { info, fields, primaryKeyField } = useCollection('users');
40
+ *
41
+ * // Using with a reactive collection name
42
+ * const collectionName = ref('articles');
43
+ * const { fields, defaults, isSingleton } = useCollection(collectionName);
44
+ *
45
+ * // Accessing properties
46
+ * console.log(info.value?.name); // Collection display name
47
+ * console.log(fields.value.length); // Number of fields
48
+ * console.log(primaryKeyField.value?.field); // Primary key field name
49
+ * ```
50
+ */
17
51
  declare function useCollection(collectionKey: string | Ref<string | null>): UsableCollection;
18
52
 
19
53
  type UsableCustomSelection = {
20
54
  otherValue: Ref<string | null>;
21
55
  usesOtherValue: ComputedRef<boolean>;
22
56
  };
57
+ /**
58
+ * A Vue composable for managing custom selection values that aren't present in a predefined list of items.
59
+ *
60
+ * This composable is typically used in form components where users can select from a predefined list
61
+ * of options, but also have the ability to enter custom values that aren't in the list. It manages
62
+ * the state and logic for detecting when a custom value is being used and provides a reactive
63
+ * interface for getting and setting custom values.
64
+ *
65
+ * @param currentValue - A reactive reference to the currently selected value. Can be null if no value is selected.
66
+ * @param items - A reactive reference to the array of available predefined items. Each item should have a 'value' property.
67
+ * @param emit - A callback function to emit value changes to the parent component.
68
+ *
69
+ * @returns An object containing:
70
+ * - `otherValue` - A computed ref for getting/setting custom values. Returns current value when using custom,
71
+ * empty string otherwise. Setting triggers the emit callback.
72
+ * - `usesOtherValue` - A computed boolean indicating whether the current value is a custom value
73
+ * (not found in the predefined items list).
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const currentValue = ref('custom-option');
78
+ * const items = ref([
79
+ * { value: 'option1', label: 'Option 1' },
80
+ * { value: 'option2', label: 'Option 2' }
81
+ * ]);
82
+ * const emit = (value: string | null) => console.log('Value changed:', value);
83
+ *
84
+ * const { otherValue, usesOtherValue } = useCustomSelection(currentValue, items, emit);
85
+ *
86
+ * console.log(usesOtherValue.value); // true (custom-option not in items)
87
+ * console.log(otherValue.value); // 'custom-option'
88
+ *
89
+ * otherValue.value = 'new-custom-value'; // Triggers emit with 'new-custom-value'
90
+ * ```
91
+ */
23
92
  declare function useCustomSelection(currentValue: Ref<string | null>, items: Ref<any[]>, emit: (event: string | null) => void): UsableCustomSelection;
24
93
  type OtherValue = {
25
94
  key: string;
@@ -31,6 +100,46 @@ type UsableCustomSelectionMultiple = {
31
100
  addOtherValue: (value?: string, focus?: boolean) => void;
32
101
  setOtherValue: (key: string, newValue: string | null) => void;
33
102
  };
103
+ /**
104
+ * A Vue composable for managing multiple custom selection values that aren't present in a predefined list of items.
105
+ *
106
+ * This composable extends the single custom selection pattern to support multiple values. It's typically used
107
+ * in multi-select form components where users can select multiple predefined options and also add custom
108
+ * values that aren't in the predefined list. It automatically detects custom values in the current selection,
109
+ * manages their state, and provides functions for adding and updating custom values.
110
+ *
111
+ * @param currentValues - A reactive reference to the currently selected values array. Can be null if no values are selected.
112
+ * @param items - A reactive reference to the array of available predefined items. Each item should have a 'value' property.
113
+ * @param emit - A callback function to emit value changes to the parent component.
114
+ *
115
+ * @returns An object containing:
116
+ * - `otherValues` - A reactive array of custom value objects, each with a unique key, value, and optional focus state.
117
+ * - `addOtherValue` - A function to add a new custom value with optional value and focus parameters.
118
+ * - `setOtherValue` - A function to update or remove a custom value by its key, automatically syncing with currentValues.
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const currentValues = ref(['option1', 'custom-value1', 'custom-value2']);
123
+ * const items = ref([
124
+ * { value: 'option1', label: 'Option 1' },
125
+ * { value: 'option2', label: 'Option 2' }
126
+ * ]);
127
+ * const emit = (values: string[] | null) => console.log('Values changed:', values);
128
+ *
129
+ * const { otherValues, addOtherValue, setOtherValue } = useCustomSelectionMultiple(currentValues, items, emit);
130
+ *
131
+ * console.log(otherValues.value); // [{ key: 'abc123', value: 'custom-value1' }, { key: 'def456', value: 'custom-value2' }]
132
+ *
133
+ * // Add a new custom value
134
+ * addOtherValue('new-custom-value', true);
135
+ *
136
+ * // Update an existing custom value
137
+ * setOtherValue('abc123', 'updated-custom-value');
138
+ *
139
+ * // Remove a custom value
140
+ * setOtherValue('def456', null);
141
+ * ```
142
+ */
34
143
  declare function useCustomSelectionMultiple(currentValues: Ref<string[] | null>, items: Ref<any[]>, emit: (event: string[] | null) => void): UsableCustomSelectionMultiple;
35
144
 
36
145
  declare global {
@@ -38,56 +147,239 @@ declare global {
38
147
  ResizeObserver: any;
39
148
  }
40
149
  }
150
+ /**
151
+ * A Vue composable that reactively tracks the size of a DOM element using ResizeObserver.
152
+ *
153
+ * @template T - The type of the element being observed, must extend Element
154
+ * @param target - The element to observe. Can be:
155
+ * - A direct element reference
156
+ * - A Vue ref containing an element
157
+ * - A Vue ref that might be undefined
158
+ *
159
+ * @returns An object containing reactive width and height values:
160
+ * - width: Ref<number> - The current width of the element in pixels
161
+ * - height: Ref<number> - The current height of the element in pixels
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * // With a template ref
166
+ * const elementRef = ref<HTMLDivElement>();
167
+ * const { width, height } = useElementSize(elementRef);
168
+ *
169
+ * // With a direct element
170
+ * const element = document.getElementById('my-element');
171
+ * const { width, height } = useElementSize(element);
172
+ * ```
173
+ *
174
+ * @remarks
175
+ * - The composable automatically sets up a ResizeObserver when the component mounts
176
+ * - The observer is automatically disconnected when the component unmounts
177
+ * - Initial values are 0 until the first resize event
178
+ * - Handles cases where the target element might be undefined
179
+ */
41
180
  declare function useElementSize<T extends Element>(target: T | Ref<T> | Ref<undefined>): {
42
181
  width: Ref<number>;
43
182
  height: Ref<number>;
44
183
  };
45
184
 
185
+ /**
186
+ * A Vue composable that filters and groups fields based on multiple filter criteria.
187
+ *
188
+ * @template T - The type of filter names as string literals
189
+ * @param fields - A Vue ref containing an array of Field objects to be filtered
190
+ * @param filters - An object where keys are filter names and values are predicate functions
191
+ * that return true if a field should be included in that group
192
+ *
193
+ * @returns An object containing:
194
+ * - fieldGroups: ComputedRef<Record<Extract<T, string>, Field[]>> - A reactive object
195
+ * where each key corresponds to a filter name and the value is an array of fields
196
+ * that pass that filter
197
+ *
198
+ * @example
199
+ * ```typescript
200
+ * // Define filter criteria
201
+ * const fieldFilters = {
202
+ * required: (field: Field) => field.required === true,
203
+ * optional: (field: Field) => field.required !== true,
204
+ * text: (field: Field) => field.type === 'string',
205
+ * numeric: (field: Field) => ['integer', 'float', 'decimal'].includes(field.type)
206
+ * };
207
+ *
208
+ * const fieldsRef = ref<Field[]>([
209
+ * { name: 'id', type: 'integer', required: true },
210
+ * { name: 'title', type: 'string', required: true },
211
+ * { name: 'description', type: 'text', required: false },
212
+ * { name: 'price', type: 'decimal', required: false }
213
+ * ]);
214
+ *
215
+ * const { fieldGroups } = useFilterFields(fieldsRef, fieldFilters);
216
+ *
217
+ * // Access filtered groups
218
+ * console.log(fieldGroups.value.required); // [id, title]
219
+ * console.log(fieldGroups.value.text); // [title]
220
+ * console.log(fieldGroups.value.numeric); // [id, price]
221
+ * ```
222
+ *
223
+ * @remarks
224
+ * - Fields can appear in multiple groups if they pass multiple filters
225
+ * - If a field doesn't pass any filter, it won't appear in any group
226
+ * - The result is reactive and will update when the input fields change
227
+ * - Filter functions are called for each field against each filter criterion
228
+ * - Groups are initialized as empty arrays even if no fields match the criteria
229
+ */
46
230
  declare function useFilterFields<T extends string>(fields: Ref<Field[]>, filters: Record<T, (field: Field) => boolean>): {
47
231
  fieldGroups: ComputedRef<Record<Extract<T, string>, Field[]>>;
48
232
  };
49
233
 
234
+ /**
235
+ * Represents a groupable item instance with its active state and value.
236
+ */
50
237
  type GroupableInstance = {
238
+ /** Reactive reference to the active state of the item */
51
239
  active: Ref<boolean>;
240
+ /** Unique identifier for the item within the group */
52
241
  value: string | number | undefined;
53
242
  };
54
243
  /**
244
+ * Configuration options for the useGroupable composable.
55
245
  * Used to make child item part of the group context. Needs to be used in a component that is a child
56
- * of a component that has the `useGroupableParent` composition enabled
246
+ * of a component that has the `useGroupableParent` composition enabled.
57
247
  */
58
248
  type GroupableOptions = {
249
+ /** Unique identifier for this groupable item */
59
250
  value?: string | number;
251
+ /** Name of the group to inject from (defaults to 'item-group') */
60
252
  group?: string;
253
+ /** External reactive reference to control the active state */
61
254
  active?: Ref<boolean>;
255
+ /** Whether to watch the external active reference for changes */
62
256
  watch?: boolean;
63
257
  };
258
+ /**
259
+ * Return type for the useGroupable composable.
260
+ */
64
261
  type UsableGroupable = {
262
+ /** Reactive reference to the active state */
65
263
  active: Ref<boolean>;
264
+ /** Toggle the active state of this item */
66
265
  toggle: () => void;
266
+ /** Activate this item (if not already active) */
67
267
  activate: () => void;
268
+ /** Deactivate this item (if currently active) */
68
269
  deactivate: () => void;
69
270
  };
271
+ /**
272
+ * Vue composable for creating groupable child items that can participate in group selection.
273
+ *
274
+ * This composable enables a component to be part of a group context managed by a parent component
275
+ * using `useGroupableParent`. It provides reactive active state management and selection control.
276
+ *
277
+ * @param options - Configuration options for the groupable item
278
+ * @param options.value - Unique identifier for this item within the group
279
+ * @param options.group - Name of the group to inject from (defaults to 'item-group')
280
+ * @param options.active - External reactive reference to control the active state
281
+ * @param options.watch - Whether to watch the external active reference for changes
282
+ *
283
+ * @returns Object containing active state and control methods
284
+ *
285
+ * @example
286
+ * ```vue
287
+ * <script setup>
288
+ * import { useGroupable } from '@directus/composables';
289
+ *
290
+ * const props = defineProps(['value', 'active']);
291
+ *
292
+ * const { active, toggle, activate, deactivate } = useGroupable({
293
+ * value: props.value,
294
+ * active: toRef(props, 'active'),
295
+ * watch: true
296
+ * });
297
+ * </script>
298
+ * ```
299
+ */
70
300
  declare function useGroupable(options?: GroupableOptions): UsableGroupable;
301
+ /**
302
+ * State configuration for the groupable parent.
303
+ */
71
304
  type GroupableParentState = {
305
+ /** External selection state (can be readonly) */
72
306
  selection?: Ref<(string | number)[] | undefined> | Ref<readonly (string | number)[] | undefined>;
307
+ /** Callback fired when the selection changes */
73
308
  onSelectionChange?: (newSelectionValues: readonly (string | number)[]) => void;
309
+ /** Callback fired when an item is toggled */
74
310
  onToggle?: (item: GroupableInstance) => void;
75
311
  };
312
+ /**
313
+ * Configuration options for the groupable parent.
314
+ */
76
315
  type GroupableParentOptions = {
316
+ /** Whether at least one item must always be selected */
77
317
  mandatory?: Ref<boolean>;
318
+ /** Maximum number of items that can be selected (-1 for unlimited) */
78
319
  max?: Ref<number>;
320
+ /** Whether multiple items can be selected simultaneously */
79
321
  multiple?: Ref<boolean>;
80
322
  };
323
+ /**
324
+ * Return type for the useGroupableParent composable.
325
+ */
81
326
  type UsableGroupableParent = {
327
+ /** Reactive array of all registered groupable items */
82
328
  items: Ref<GroupableInstance[]>;
329
+ /** Computed selection array (readonly) */
83
330
  selection: Ref<readonly (string | number)[]>;
331
+ /** Internal selection state */
84
332
  internalSelection: Ref<(string | number)[]>;
333
+ /** Get the value identifier for a given item */
85
334
  getValueForItem: (item: GroupableInstance) => string | number;
335
+ /** Synchronize children's active state with selection */
86
336
  updateChildren: () => void;
87
337
  };
88
338
  /**
89
- * Used to make a component a group parent component. Provides the registration / toggle functions
90
- * to its group children
339
+ * Vue composable for creating a group parent that manages multiple groupable child items.
340
+ *
341
+ * This composable provides the foundation for components that need to manage a collection
342
+ * of selectable items, such as tabs, radio groups, or multi-select lists. It handles
343
+ * registration of child items, selection state management, and provides various selection
344
+ * constraints (mandatory, maximum, multiple).
345
+ *
346
+ * @param state - External state configuration for selection management
347
+ * @param state.selection - External selection state reference
348
+ * @param state.onSelectionChange - Callback fired when selection changes
349
+ * @param state.onToggle - Callback fired when an item is toggled
350
+ * @param options - Configuration options for selection behavior
351
+ * @param options.mandatory - Whether at least one item must always be selected
352
+ * @param options.max - Maximum number of items that can be selected (-1 for unlimited)
353
+ * @param options.multiple - Whether multiple items can be selected simultaneously
354
+ * @param group - Injection key for the group (defaults to 'item-group')
355
+ *
356
+ * @returns Object containing items array, selection state, and utility functions
357
+ *
358
+ * @example
359
+ * ```vue
360
+ * <script setup>
361
+ * import { useGroupableParent } from '@directus/composables';
362
+ * import { ref } from 'vue';
363
+ *
364
+ * const selectedItems = ref([]);
365
+ * const isMultiple = ref(true);
366
+ * const isMandatory = ref(false);
367
+ *
368
+ * const { items, selection } = useGroupableParent(
369
+ * {
370
+ * selection: selectedItems,
371
+ * onSelectionChange: (values) => {
372
+ * console.log('Selection changed:', values);
373
+ * }
374
+ * },
375
+ * {
376
+ * multiple: isMultiple,
377
+ * mandatory: isMandatory,
378
+ * max: ref(3)
379
+ * }
380
+ * );
381
+ * </script>
382
+ * ```
91
383
  */
92
384
  declare function useGroupableParent(state?: GroupableParentState, options?: GroupableParentOptions, group?: string): UsableGroupableParent;
93
385
 
@@ -121,10 +413,127 @@ type ComputedQuery = {
121
413
  };
122
414
  declare function useItems(collection: Ref<string | null>, query: ComputedQuery): UsableItems;
123
415
 
416
+ declare const WRITABLE_PROPS: readonly ["selection", "layoutOptions", "layoutQuery"];
417
+ type WritableProp = (typeof WRITABLE_PROPS)[number];
418
+ /**
419
+ * Type guard to check if a property is writable (can be updated via emit).
420
+ *
421
+ * This function determines whether a given property name corresponds to one of the
422
+ * writable properties that can be updated through Vue's emit system.
423
+ *
424
+ * @param prop - The property name to check
425
+ * @returns True if the property is writable, false otherwise
426
+ *
427
+ * @example
428
+ * ```typescript
429
+ * if (isWritableProp('selection')) {
430
+ * // Property is writable, can emit update
431
+ * emit('update:selection', newValue);
432
+ * }
433
+ * ```
434
+ */
435
+ declare function isWritableProp(prop: string): prop is WritableProp;
436
+ /**
437
+ * Creates a Vue component wrapper for a layout configuration.
438
+ *
439
+ * This function creates a dynamic Vue component that wraps a layout with standardized
440
+ * props, emits, and state management. It handles reactive state updates, prop validation,
441
+ * and provides a consistent interface for all layout components.
442
+ *
443
+ * @template Options - The type for layout-specific options
444
+ * @template Query - The type for layout-specific query parameters
445
+ * @param layout - The layout configuration object containing id and setup function
446
+ * @returns A Vue component that can be used to render the layout
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * interface MyLayoutOptions {
451
+ * itemSize: number;
452
+ * showHeaders: boolean;
453
+ * }
454
+ *
455
+ * interface MyLayoutQuery {
456
+ * page: number;
457
+ * limit: number;
458
+ * }
459
+ *
460
+ * const layoutConfig: LayoutConfig = {
461
+ * id: 'my-layout',
462
+ * setup: (props, { emit }) => ({
463
+ * // Layout-specific setup logic
464
+ * })
465
+ * };
466
+ *
467
+ * const LayoutWrapper = createLayoutWrapper<MyLayoutOptions, MyLayoutQuery>(layoutConfig);
468
+ * ```
469
+ */
470
+ declare function createLayoutWrapper<Options, Query>(layout: LayoutConfig): Component;
471
+ /**
472
+ * Composable for managing layout components in Directus.
473
+ *
474
+ * This composable provides access to layout components and handles the dynamic
475
+ * selection of layout wrappers based on the provided layout ID. It automatically
476
+ * falls back to the tabular layout if the requested layout is not found.
477
+ *
478
+ * @template Options - The type for layout-specific options (default: any)
479
+ * @template Query - The type for layout-specific query parameters (default: any)
480
+ * @param layoutId - A reactive reference to the layout ID
481
+ * @returns An object containing the layout wrapper component
482
+ *
483
+ * @example
484
+ * ```typescript
485
+ * import { ref } from 'vue';
486
+ * import { useLayout } from './use-layout';
487
+ *
488
+ * const selectedLayoutId = ref('table');
489
+ * const { layoutWrapper } = useLayout(selectedLayoutId);
490
+ *
491
+ * // Use the layout wrapper in your template
492
+ * // <component :is="layoutWrapper" :collection="'users'" />
493
+ * ```
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * // With typed options and query
498
+ * interface TableOptions {
499
+ * spacing: 'cozy' | 'comfortable' | 'compact';
500
+ * showHeaders: boolean;
501
+ * }
502
+ *
503
+ * interface TableQuery {
504
+ * sort: string[];
505
+ * limit: number;
506
+ * }
507
+ *
508
+ * const layoutId = ref<string | null>('table');
509
+ * const { layoutWrapper } = useLayout<TableOptions, TableQuery>(layoutId);
510
+ * ```
511
+ */
124
512
  declare function useLayout<Options = any, Query = any>(layoutId: Ref<string | null>): {
125
513
  layoutWrapper: ComputedRef<Component>;
126
514
  };
127
515
 
516
+ /**
517
+ * Vue props definition for size-related boolean properties.
518
+ *
519
+ * This object defines the standard size props that can be used in Vue components
520
+ * to control size-based styling through CSS classes.
521
+ *
522
+ * @example
523
+ * ```typescript
524
+ * // In a Vue component
525
+ * export default defineComponent({
526
+ * props: {
527
+ * ...sizeProps,
528
+ * // other props
529
+ * },
530
+ * setup(props) {
531
+ * const sizeClass = useSizeClass(props);
532
+ * return { sizeClass };
533
+ * }
534
+ * });
535
+ * ```
536
+ */
128
537
  declare const sizeProps: {
129
538
  xSmall: {
130
539
  type: BooleanConstructor;
@@ -149,13 +558,500 @@ interface SizeProps {
149
558
  large?: boolean;
150
559
  xLarge?: boolean;
151
560
  }
561
+ /**
562
+ * Composable for generating CSS size class names based on size props.
563
+ *
564
+ * This composable takes props containing size boolean flags and returns a computed
565
+ * CSS class name string. It follows a priority order: xSmall > small > large > xLarge.
566
+ * If no size props are true, it returns null.
567
+ *
568
+ * @template T - The type of additional props that extend SizeProps
569
+ * @param props - The props object containing size boolean properties
570
+ * @returns A computed ref that resolves to the appropriate CSS class name or null
571
+ *
572
+ * @example
573
+ * ```typescript
574
+ * // Basic usage in a Vue component
575
+ * const props = { small: true, large: false };
576
+ * const sizeClass = useSizeClass(props);
577
+ * console.log(sizeClass.value); // 'small'
578
+ * ```
579
+ *
580
+ * @example
581
+ * ```typescript
582
+ * // Usage with additional props
583
+ * interface MyProps {
584
+ * color: string;
585
+ * disabled: boolean;
586
+ * }
587
+ *
588
+ * const props: MyProps & SizeProps = {
589
+ * color: 'blue',
590
+ * disabled: false,
591
+ * xLarge: true
592
+ * };
593
+ *
594
+ * const sizeClass = useSizeClass(props);
595
+ * console.log(sizeClass.value); // 'x-large'
596
+ * ```
597
+ *
598
+ * @example
599
+ * ```typescript
600
+ * // In a Vue component with reactive props
601
+ * export default defineComponent({
602
+ * props: {
603
+ * ...sizeProps,
604
+ * label: String,
605
+ * },
606
+ * setup(props) {
607
+ * const sizeClass = useSizeClass(props);
608
+ *
609
+ * return { sizeClass };
610
+ * },
611
+ * template: `
612
+ * <button :class="['btn', sizeClass]">
613
+ * {{ label }}
614
+ * </button>
615
+ * `
616
+ * });
617
+ * ```
618
+ */
152
619
  declare function useSizeClass<T>(props: T & SizeProps): ComputedRef<string | null>;
153
620
 
621
+ /**
622
+ * Composable for creating two-way binding between parent and child components.
623
+ *
624
+ * @deprecated Use Vue's native `defineModel()` instead. This composable is kept for backward compatibility.
625
+ * Vue 3.4+ provides `defineModel()` which offers a more streamlined and performant way to create v-model bindings.
626
+ *
627
+ * @see {@link https://vuejs.org/api/sfc-script-setup.html#definemodel} Vue's defineModel documentation
628
+ *
629
+ * This composable creates a computed ref that synchronizes a prop value with
630
+ * its parent component through Vue's v-model pattern. It provides a getter
631
+ * that returns the current prop value and a setter that emits an update event
632
+ * to notify the parent component of changes.
633
+ *
634
+ * This is particularly useful for creating custom form components that need
635
+ * to work with v-model while maintaining proper data flow patterns.
636
+ *
637
+ * @template T - The type of the props object
638
+ * @template K - The key of the prop to sync (must be a string key of T)
639
+ * @template E - The emit function type with proper event typing
640
+ *
641
+ * @param props - The component props object containing the value to sync
642
+ * @param key - The specific prop key to create a two-way binding for
643
+ * @param emit - The Vue emit function for sending update events to parent
644
+ *
645
+ * @returns A computed ref that can be used with v-model pattern
646
+ *
647
+ * @example
648
+ * ```typescript
649
+ * // DEPRECATED: Old way using useSync
650
+ * export default defineComponent({
651
+ * props: {
652
+ * modelValue: String,
653
+ * disabled: Boolean,
654
+ * },
655
+ * emits: ['update:modelValue'],
656
+ * setup(props, { emit }) {
657
+ * const syncedValue = useSync(props, 'modelValue', emit);
658
+ * return { syncedValue };
659
+ * }
660
+ * });
661
+ *
662
+ * // RECOMMENDED: New way using defineModel (Vue 3.4+)
663
+ * <script setup lang="ts">
664
+ * const modelValue = defineModel<string>();
665
+ * const disabled = defineProps<{ disabled?: boolean }>();
666
+ * </script>
667
+ *
668
+ * <template>
669
+ * <input v-model="modelValue" :disabled="disabled" />
670
+ * </template>
671
+ * ```
672
+ *
673
+ * @example
674
+ * ```typescript
675
+ * // DEPRECATED: Custom input component with useSync
676
+ * interface Props {
677
+ * value: string;
678
+ * placeholder?: string;
679
+ * type?: string;
680
+ * }
681
+ *
682
+ * export default defineComponent({
683
+ * props: {
684
+ * value: { type: String, required: true },
685
+ * placeholder: String,
686
+ * type: { type: String, default: 'text' },
687
+ * },
688
+ * emits: ['update:value'],
689
+ * setup(props: Props, { emit }) {
690
+ * const syncedValue = useSync(props, 'value', emit);
691
+ * return { syncedValue };
692
+ * }
693
+ * });
694
+ *
695
+ * // RECOMMENDED: Using defineModel with custom prop name
696
+ * <script setup lang="ts">
697
+ * const value = defineModel<string>('value', { required: true });
698
+ * const { placeholder, type = 'text' } = defineProps<{
699
+ * placeholder?: string;
700
+ * type?: string;
701
+ * }>();
702
+ * </script>
703
+ * ```
704
+ *
705
+ * @example
706
+ * ```typescript
707
+ * // DEPRECATED: Usage with complex objects using useSync
708
+ * interface UserData {
709
+ * name: string;
710
+ * email: string;
711
+ * age: number;
712
+ * }
713
+ *
714
+ * export default defineComponent({
715
+ * props: {
716
+ * userData: { type: Object as PropType<UserData>, required: true },
717
+ * isLoading: Boolean,
718
+ * },
719
+ * emits: ['update:userData'],
720
+ * setup(props, { emit }) {
721
+ * const syncedUserData = useSync(props, 'userData', emit);
722
+ *
723
+ * const updateName = (newName: string) => {
724
+ * syncedUserData.value = {
725
+ * ...syncedUserData.value,
726
+ * name: newName
727
+ * };
728
+ * };
729
+ *
730
+ * return { syncedUserData, updateName };
731
+ * }
732
+ * });
733
+ *
734
+ * // RECOMMENDED: Using defineModel with complex objects
735
+ * <script setup lang="ts">
736
+ * interface UserData {
737
+ * name: string;
738
+ * email: string;
739
+ * age: number;
740
+ * }
741
+ *
742
+ * const userData = defineModel<UserData>('userData', { required: true });
743
+ * const { isLoading } = defineProps<{ isLoading?: boolean }>();
744
+ *
745
+ * const updateName = (newName: string) => {
746
+ * userData.value = {
747
+ * ...userData.value,
748
+ * name: newName
749
+ * };
750
+ * };
751
+ * </script>
752
+ * ```
753
+ */
154
754
  declare function useSync<T, K extends keyof T & string, E extends (event: `update:${K}`, ...args: any[]) => void>(props: T, key: K, emit: E): Ref<T[K]>;
155
755
 
756
+ /**
757
+ * Vue composable that provides access to the global Directus stores through dependency injection.
758
+ *
759
+ * This composable injects the stores object that contains all the Pinia stores used throughout
760
+ * the Directus application, including user store, permissions store, collections store, etc.
761
+ *
762
+ * @returns The injected stores object containing all application stores
763
+ * @throws Error if the stores could not be found in the injection context
764
+ *
765
+ * @example
766
+ * ```typescript
767
+ * import { useStores } from '@directus/composables';
768
+ *
769
+ * export default defineComponent({
770
+ * setup() {
771
+ * const stores = useStores();
772
+ *
773
+ * // Access specific stores
774
+ * const userStore = stores.useUserStore();
775
+ * const collectionsStore = stores.useCollectionsStore();
776
+ * const permissionsStore = stores.usePermissionsStore();
777
+ *
778
+ * return {
779
+ * userInfo: userStore.currentUser,
780
+ * collections: collectionsStore.collections,
781
+ * permissions: permissionsStore.permissions
782
+ * };
783
+ * }
784
+ * });
785
+ * ```
786
+ *
787
+ * @example
788
+ * ```typescript
789
+ * // Using in a component with reactive store data
790
+ * import { useStores } from '@directus/composables';
791
+ * import { computed } from 'vue';
792
+ *
793
+ * export default defineComponent({
794
+ * setup() {
795
+ * const stores = useStores();
796
+ * const userStore = stores.useUserStore();
797
+ *
798
+ * const isAdmin = computed(() => {
799
+ * return userStore.currentUser?.role?.admin_access === true;
800
+ * });
801
+ *
802
+ * const hasCreatePermission = computed(() => {
803
+ * const permissionsStore = stores.usePermissionsStore();
804
+ * return permissionsStore.hasPermission('directus_files', 'create');
805
+ * });
806
+ *
807
+ * return { isAdmin, hasCreatePermission };
808
+ * }
809
+ * });
810
+ * ```
811
+ */
156
812
  declare function useStores(): Record<string, any>;
813
+ /**
814
+ * Vue composable that provides access to the Axios HTTP client instance through dependency injection.
815
+ *
816
+ * This composable injects the configured Axios instance that is set up with the proper base URL,
817
+ * authentication headers, interceptors, and other configuration needed to communicate with the
818
+ * Directus API. It provides a convenient way to make HTTP requests from components and composables.
819
+ *
820
+ * @returns The injected Axios instance configured for Directus API communication
821
+ * @throws Error if the API instance could not be found in the injection context
822
+ *
823
+ * @example
824
+ * ```typescript
825
+ * import { useApi } from '@directus/composables';
826
+ *
827
+ * export default defineComponent({
828
+ * setup() {
829
+ * const api = useApi();
830
+ *
831
+ * const fetchUserData = async (userId: string) => {
832
+ * try {
833
+ * const response = await api.get(`/users/${userId}`);
834
+ * return response.data;
835
+ * } catch (error) {
836
+ * console.error('Failed to fetch user data:', error);
837
+ * throw error;
838
+ * }
839
+ * };
840
+ *
841
+ * return { fetchUserData };
842
+ * }
843
+ * });
844
+ * ```
845
+ *
846
+ * @example
847
+ * ```typescript
848
+ * // Using with reactive data and error handling
849
+ * import { useApi } from '@directus/composables';
850
+ * import { ref, onMounted } from 'vue';
851
+ *
852
+ * export default defineComponent({
853
+ * setup() {
854
+ * const api = useApi();
855
+ * const collections = ref([]);
856
+ * const loading = ref(false);
857
+ * const error = ref(null);
858
+ *
859
+ * const loadCollections = async () => {
860
+ * loading.value = true;
861
+ * error.value = null;
862
+ *
863
+ * try {
864
+ * const response = await api.get('/collections');
865
+ * collections.value = response.data.data;
866
+ * } catch (err) {
867
+ * error.value = err.response?.data?.errors?.[0]?.message || 'Failed to load collections';
868
+ * } finally {
869
+ * loading.value = false;
870
+ * }
871
+ * };
872
+ *
873
+ * onMounted(loadCollections);
874
+ *
875
+ * return { collections, loading, error, loadCollections };
876
+ * }
877
+ * });
878
+ * ```
879
+ */
157
880
  declare function useApi(): AxiosInstance;
881
+ /**
882
+ * Vue composable that provides access to the Directus SDK client instance through dependency injection.
883
+ *
884
+ * This composable injects the configured Directus SDK client that provides a type-safe, modern API
885
+ * for interacting with Directus. The SDK offers methods for CRUD operations, authentication, file
886
+ * management, and more, with full TypeScript support and automatic type inference based on your schema.
887
+ *
888
+ * @template Schema - The TypeScript schema type for your Directus instance, defaults to `any`
889
+ * @returns The injected Directus SDK client with REST client capabilities
890
+ * @throws Error if the SDK instance could not be found in the injection context
891
+ *
892
+ * @example
893
+ * ```typescript
894
+ * import { useSdk } from '@directus/composables';
895
+ *
896
+ * // Using with default schema
897
+ * export default defineComponent({
898
+ * setup() {
899
+ * const sdk = useSdk();
900
+ *
901
+ * const fetchArticles = async () => {
902
+ * try {
903
+ * const articles = await sdk.items('articles').readByQuery({
904
+ * filter: { status: { _eq: 'published' } },
905
+ * sort: ['-date_created'],
906
+ * limit: 10
907
+ * });
908
+ * return articles;
909
+ * } catch (error) {
910
+ * console.error('Failed to fetch articles:', error);
911
+ * throw error;
912
+ * }
913
+ * };
914
+ *
915
+ * return { fetchArticles };
916
+ * }
917
+ * });
918
+ * ```
919
+ *
920
+ * @example
921
+ * ```typescript
922
+ * // Using with typed schema for better type safety
923
+ * import { useSdk } from '@directus/composables';
924
+ *
925
+ * interface MySchema {
926
+ * articles: {
927
+ * id: string;
928
+ * title: string;
929
+ * content: string;
930
+ * status: 'draft' | 'published';
931
+ * author: string;
932
+ * date_created: string;
933
+ * };
934
+ * authors: {
935
+ * id: string;
936
+ * name: string;
937
+ * email: string;
938
+ * };
939
+ * }
940
+ *
941
+ * export default defineComponent({
942
+ * setup() {
943
+ * const sdk = useSdk<MySchema>();
944
+ *
945
+ * const createArticle = async (articleData: Partial<MySchema['articles']>) => {
946
+ * try {
947
+ * const newArticle = await sdk.items('articles').createOne(articleData);
948
+ * return newArticle; // Fully typed return value
949
+ * } catch (error) {
950
+ * console.error('Failed to create article:', error);
951
+ * throw error;
952
+ * }
953
+ * };
954
+ *
955
+ * const updateArticle = async (id: string, updates: Partial<MySchema['articles']>) => {
956
+ * try {
957
+ * const updatedArticle = await sdk.items('articles').updateOne(id, updates);
958
+ * return updatedArticle; // Type-safe updates
959
+ * } catch (error) {
960
+ * console.error('Failed to update article:', error);
961
+ * throw error;
962
+ * }
963
+ * };
964
+ *
965
+ * return { createArticle, updateArticle };
966
+ * }
967
+ * });
968
+ * ```
969
+ */
158
970
  declare function useSdk<Schema extends object = any>(): DirectusClient<Schema> & RestClient<Schema>;
971
+ /**
972
+ * Vue composable that provides access to the registered Directus extensions through dependency injection.
973
+ *
974
+ * This composable injects the extensions configuration object that contains all registered app
975
+ * extensions including interfaces, displays, layouts, modules, panels, operations, and more.
976
+ * The extensions are provided as reactive references and can be used to dynamically access
977
+ * and utilize custom functionality within the Directus application.
978
+ *
979
+ * @returns A reactive record of extension configurations organized by extension type
980
+ * @throws Error if the extensions could not be found in the injection context
981
+ *
982
+ * @example
983
+ * ```typescript
984
+ * import { useExtensions } from '@directus/composables';
985
+ *
986
+ * export default defineComponent({
987
+ * setup() {
988
+ * const extensions = useExtensions();
989
+ *
990
+ * const getAvailableInterfaces = () => {
991
+ * return Object.values(extensions.interfaces || {});
992
+ * };
993
+ *
994
+ * const getAvailableDisplays = () => {
995
+ * return Object.values(extensions.displays || {});
996
+ * };
997
+ *
998
+ * const findInterfaceByName = (name: string) => {
999
+ * return extensions.interfaces?.[name] || null;
1000
+ * };
1001
+ *
1002
+ * return {
1003
+ * getAvailableInterfaces,
1004
+ * getAvailableDisplays,
1005
+ * findInterfaceByName
1006
+ * };
1007
+ * }
1008
+ * });
1009
+ * ```
1010
+ *
1011
+ * @example
1012
+ * ```typescript
1013
+ * // Using with computed properties for reactive extension lists
1014
+ * import { useExtensions } from '@directus/composables';
1015
+ * import { computed } from 'vue';
1016
+ *
1017
+ * export default defineComponent({
1018
+ * setup() {
1019
+ * const extensions = useExtensions();
1020
+ *
1021
+ * const availableLayouts = computed(() => {
1022
+ * return Object.entries(extensions.layouts || {}).map(([key, config]) => ({
1023
+ * id: key,
1024
+ * name: config.name,
1025
+ * icon: config.icon,
1026
+ * component: config.component
1027
+ * }));
1028
+ * });
1029
+ *
1030
+ * const customModules = computed(() => {
1031
+ * return Object.values(extensions.modules || {}).filter(module =>
1032
+ * !module.preRegisterCheck || module.preRegisterCheck()
1033
+ * );
1034
+ * });
1035
+ *
1036
+ * const operationsByGroup = computed(() => {
1037
+ * const operations = Object.values(extensions.operations || {});
1038
+ * return operations.reduce((groups, operation) => {
1039
+ * const group = operation.overview?.group || 'other';
1040
+ * if (!groups[group]) groups[group] = [];
1041
+ * groups[group].push(operation);
1042
+ * return groups;
1043
+ * }, {} as Record<string, any[]>);
1044
+ * });
1045
+ *
1046
+ * return {
1047
+ * availableLayouts,
1048
+ * customModules,
1049
+ * operationsByGroup
1050
+ * };
1051
+ * }
1052
+ * });
1053
+ * ```
1054
+ */
159
1055
  declare function useExtensions(): RefRecord<AppExtensionConfigs>;
160
1056
 
161
- export { type ComputedQuery, type GroupableInstance, type GroupableOptions, type ManualSortData, type OtherValue, type UsableCollection, type UsableCustomSelection, type UsableGroupable, type UsableItems, sizeProps, useApi, useCollection, useCustomSelection, useCustomSelectionMultiple, useElementSize, useExtensions, useFilterFields, useGroupable, useGroupableParent, useItems, useLayout, useSdk, useSizeClass, useStores, useSync };
1057
+ export { type ComputedQuery, type GroupableInstance, type GroupableOptions, type ManualSortData, type OtherValue, type UsableCollection, type UsableCustomSelection, type UsableGroupable, type UsableItems, createLayoutWrapper, isWritableProp, sizeProps, useApi, useCollection, useCustomSelection, useCustomSelectionMultiple, useElementSize, useExtensions, useFilterFields, useGroupable, useGroupableParent, useItems, useLayout, useSdk, useSizeClass, useStores, useSync };
package/dist/index.js CHANGED
@@ -61,9 +61,7 @@ function useCollection(collectionKey) {
61
61
  return info.value?.meta?.singleton === true;
62
62
  });
63
63
  const accountabilityScope = computed(() => {
64
- if (!info.value) return null;
65
- if (!info.value.meta) return null;
66
- return info.value.meta.accountability;
64
+ return info.value?.meta?.accountability || null;
67
65
  });
68
66
  return { info, fields, defaults, primaryKeyField, userCreatedField, sortField, isSingleton, accountabilityScope };
69
67
  }
@@ -684,6 +682,8 @@ function useSync(props, key, emit) {
684
682
  });
685
683
  }
686
684
  export {
685
+ createLayoutWrapper,
686
+ isWritableProp,
687
687
  sizeProps,
688
688
  useApi,
689
689
  useCollection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/composables",
3
- "version": "11.2.0",
3
+ "version": "11.2.2",
4
4
  "description": "Shared Vue composables for Directus use",
5
5
  "homepage": "https://directus.io",
6
6
  "repository": {
@@ -22,31 +22,32 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "axios": "1.8.4",
25
+ "axios": "1.11.0",
26
26
  "lodash-es": "4.17.21",
27
- "nanoid": "5.1.2",
28
- "@directus/constants": "13.0.1",
29
- "@directus/utils": "13.0.7"
27
+ "nanoid": "5.1.5",
28
+ "@directus/utils": "13.0.9",
29
+ "@directus/constants": "13.0.2"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@directus/tsconfig": "3.0.0",
33
33
  "@types/lodash-es": "4.17.12",
34
- "@vitest/coverage-v8": "2.1.9",
34
+ "@vitest/coverage-v8": "3.2.4",
35
35
  "@vue/test-utils": "2.4.6",
36
- "tsup": "8.4.0",
37
- "typescript": "5.8.2",
38
- "vitest": "2.1.9",
39
- "vue": "3.5.13",
40
- "@directus/extensions": "3.0.7",
41
- "@directus/sdk": "20.0.0",
42
- "@directus/types": "13.2.0"
36
+ "tsup": "8.5.0",
37
+ "typescript": "5.8.3",
38
+ "vitest": "3.2.4",
39
+ "vue": "3.5.18",
40
+ "@directus/extensions": "3.0.9",
41
+ "@directus/sdk": "20.0.2",
42
+ "@directus/types": "13.2.1"
43
43
  },
44
44
  "peerDependencies": {
45
- "vue": "^3.4"
45
+ "vue": "3.5.18"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsup src/index.ts --format=esm --dts",
49
49
  "dev": "tsup src/index.ts --format=esm --dts --watch",
50
- "test": "vitest --watch=false"
50
+ "test": "vitest run",
51
+ "test:coverage": "vitest run --coverage"
51
52
  }
52
53
  }