Skip to content

Public Factories

@nhtio/lucid-resourceful-vue-components/factories

Specialized utility functions and types for creating Vue components with enhanced props and emits handling. These factories solve common TypeScript and Vue integration challenges by automating emit handler prop generation, providing type-safe defaults extraction, and supporting both camelCase and kebab-case naming conventions.

Tip

These factories are used internally by the library's components (ResourcefulRecord, TimezonePicker, Sortable, fields) but are also exported for building your own custom components that integrate seamlessly with the library's patterns.

Core factory functions

propsFactory

Purpose: Creates a prop factory function that accepts default values and merges them with the base props definition.

Use case:

When building reusable components with configurable defaults. Allows consumers to override defaults at component instantiation while preserving TypeScript intellisense for all props.

Example:

ts
import { propsFactory } from '@nhtio/lucid-resourceful-vue-components/factories'

const baseProps = {
  color: { type: String, default: 'primary' },
  disabled: { type: Boolean, default: false },
}

const makeMyComponentProps = propsFactory(baseProps, 'MyComponent')

export default defineComponent({
  props: makeMyComponentProps({
    color: 'secondary', // Override default
  }),
})

emitsFactory

Purpose: Converts emit validator functions (returning boolean) to Vue-compatible emit functions (returning void) while preserving runtime validation logic.

Use case:

Solves the TypeScript integration challenge where Vue expects emit functions to return void but developers want validator functions that return boolean for validation logic. The factory preserves runtime validation while providing correct TypeScript types.

Example:

ts
import { emitsFactory } from '@nhtio/lucid-resourceful-vue-components/factories'

const emitValidators = {
  'update:modelValue': (value: string[]) => Array.isArray(value),
  'error': (error: unknown) => true,
  'save': (data: { id: string }) => data && typeof data.id === 'string',
}

export default defineComponent({
  emits: emitsFactory(emitValidators),
  setup(props, { emit }) {
    // emit functions are properly typed as (...args) => void
    emit('update:modelValue', ['item1', 'item2'])
    emit('error', new Error('Failed'))
  },
})

propsAndEmitsFactory

Purpose: Creates both props and emits for Vue components with automatic emit handler prop generation. Supports both camelCase and kebab-case naming conventions for maximum template syntax flexibility.

Use case:

Building components that need to expose events as both emits and optional prop handlers (e.g., @update:modelValue or :onUpdate:modelValue). The factory automatically generates corresponding prop handlers for each emit event, ensuring type safety throughout component development.

Example:

ts
import { propsAndEmitsFactory } from '@nhtio/lucid-resourceful-vue-components/factories'

const componentProps = {
  value: String,
  disabled: Boolean,
}

const componentEmits = {
  'update:modelValue': (value: string) => typeof value === 'string',
  'item:selected': (item: { id: string }) => item && typeof item.id === 'string',
}

const { makeProps, emits } = propsAndEmitsFactory(
  componentProps,
  componentEmits,
  'MyComponent'
)

export default defineComponent({
  props: makeProps(),
  emits,
  setup(props, { emit }) {
    // Props include original props + generated emit handlers:
    // - value, disabled
    // - onUpdate:modelValue, onUpdateModelValue
    // - onItem:selected, onItemSelected
    props.onUpdateModelValue?.(newValue)
    emit('update:modelValue', newValue)
  },
})

Template usage:

vue
<!-- Both syntaxes supported -->
<MyComponent :onUpdate:modelValue="handler" />
<MyComponent @update:model-value="handler" />
<MyComponent :onItem:selected="handler" />

propsAndDefaultsFactory

Purpose: Creates a prop factory and extracts default values as a separate typed object. Useful for component groups that need to share default values or for programmatic access to defaults.

Use case:

Internal library pattern used in ResourcefulRecord and field components where default prop values need to be accessible outside the component definition (e.g., for validation, state management, or sharing defaults across related components).

Example:

ts
import { propsAndDefaultsFactory } from '@nhtio/lucid-resourceful-vue-components/factories'
import type { ExtractPublicPropTypesFromFactory } from '@nhtio/lucid-resourceful-vue-components/factories'

const { factory: makeMyComponentProps, defaults: myComponentDefaults } =
  propsAndDefaultsFactory(
    {
      color: { type: String, default: 'primary' },
      size: { type: String, default: 'medium' },
      disabled: { type: Boolean, default: false },
    },
    'MyComponent'
  )

// Type-safe default values object
// myComponentDefaults = { color: 'primary', size: 'medium', disabled: false }

// Use in component
export default defineComponent({
  props: makeMyComponentProps(),
})

// Type extraction
export type MyComponentProps = ExtractPublicPropTypesFromFactory<typeof makeMyComponentProps>

getCamelAndMaybeKebabPropKeysFor

Purpose: Converts an emit event name to both camelCase and kebab-case prop handler names (e.g., update:modelValueonUpdate:modelValue and onUpdateModelValue).

Use case:

Internal utility used by propsAndEmitsFactory to generate prop handler keys. Exposed for advanced use cases where you need to programmatically derive prop handler names from event names.

Example:

ts
import { getCamelAndMaybeKebabPropKeysFor } from '@nhtio/lucid-resourceful-vue-components/factories'

const { camel, kebab } = getCamelAndMaybeKebabPropKeysFor('update:modelValue')
// camel: 'onUpdateModelValue'
// kebab: 'onUpdate:modelValue'

const { camel: camel2, kebab: kebab2 } = getCamelAndMaybeKebabPropKeysFor('item:selected')
// camel2: 'onItemSelected'
// kebab2: 'onItem:selected'

pick

Purpose: Creates a new prop factory by selecting specific properties from an existing prop factory. Returns a subset containing only the specified keys.

Use case:

Building specialized components that extend a base component but only expose a limited set of props. Useful for creating wrapper components that provide opinionated defaults while preserving type safety.

Example:

ts
import { propsFactory, pick } from '@nhtio/lucid-resourceful-vue-components/factories'
import { makeVButtonProps } from 'vuetify/components/VBtn'

// Create a specialized button that only exposes color, size, and disabled
const makeIconButtonProps = propsFactory(
  {
    ...pick(makeVButtonProps(), ['color', 'size', 'disabled']),
    icon: { type: String, required: true },
  },
  'IconButton'
)

export default defineComponent({
  props: makeIconButtonProps(),
  // Component only has: color, size, disabled, icon
  // Other VBtn props (variant, elevation, etc.) are excluded
})

omit

Purpose: Creates a new prop factory by excluding specific properties from an existing prop factory. Returns a subset with the specified keys removed.

Use case:

Building components that wrap existing components but need to override or hide certain props. Prevents prop conflicts and provides cleaner component interfaces by explicitly removing props that shouldn't be exposed.

Example:

ts
import { propsFactory, omit } from '@nhtio/lucid-resourceful-vue-components/factories'
import { makeVTextFieldProps } from 'vuetify/components/VTextField'

// Create a search input that removes type and prepends an icon
const makeSearchFieldProps = propsFactory(
  {
    ...omit(makeVTextFieldProps(), ['type', 'prependInnerIcon']),
    // Override with fixed values
    type: { type: String, default: 'search' },
    prependInnerIcon: { type: String, default: 'mdi-magnify' },
  },
  'SearchField'
)

export default defineComponent({
  props: makeSearchFieldProps(),
  // Component has all VTextField props except original type/prependInnerIcon
  // Uses search-specific defaults instead
})

Advanced pattern:

ts
import { propsFactory, pick, omit } from '@nhtio/lucid-resourceful-vue-components/factories'
import { makeControlButtonProps } from './control-button'

// Complex prop composition
const makeToolbarButtonProps = propsFactory(
  {
    // Include only visual props, exclude behavior props
    ...pick(makeControlButtonProps(), ['color', 'size', 'density', 'elevation']),
    // Add toolbar-specific props
    tooltip: { type: String },
    shortcut: { type: String },
  },
  'ToolbarButton'
)

// Or exclude specific props while keeping the rest
const makeReadonlyButtonProps = propsFactory(
  {
    ...omit(makeControlButtonProps(), ['onClick', 'to', 'href']),
    // Force readonly state
    state: { type: Object, default: () => ({ disabled: true, readonly: true }) },
  },
  'ReadonlyButton'
)

Type utilities

PropFactory<PropsOptions>

Purpose: Type representing a prop factory function that accepts optional defaults and returns merged props with proper TypeScript types.

Use case:

Typing your own prop factory functions or extracting prop types from existing factories.

CastToEmitValidators<T>

Purpose: Transforms emit functions (returning void) back to emit validators (returning boolean) for type manipulation scenarios.

Use case:

Advanced type transformations when working with emit types in library-level code.

EmitHandlerProps<E>

Purpose: Generates prop handler types from emit validators. Creates both camelCase and kebab-case variants for each emit.

Use case:

Understanding the prop types generated by propsAndEmitsFactory or building custom emit-handler prop patterns.

HookableEvents<T>

Purpose: Transforms emit validators into hookable event types with two signatures: hook handler and cleanup function (with error parameter).

Use case:

Advanced pattern used in ResourcefulRecord for lifecycle hooks that support both async execution and error handling. Converts emits like saving into tuples for hook registration and cleanup.

Example:

ts
import type { HookableEvents } from '@nhtio/lucid-resourceful-vue-components/factories'

const emits = {
  saving: (model: BaseModel) => true,
  finding: (query: QueryBuilder) => true,
}

type Events = HookableEvents<typeof emits>
// {
//   saving: [
//     [BaseModel],                      // hook handler signature
//     [error: Error | null, BaseModel], // cleanup function signature
//   ],
//   finding: [
//     [QueryBuilder],
//     [error: Error | null, QueryBuilder],
//   ]
// }

ExtractPublicPropTypesFromFactory<T>

Purpose: Extracts public prop types from a prop factory function.

Use case:

Deriving component prop types for external consumption or TypeScript interfaces.

Example:

ts
import type { ExtractPublicPropTypesFromFactory } from '@nhtio/lucid-resourceful-vue-components/factories'

const makeProps = propsFactory({ color: String, disabled: Boolean }, 'MyComponent')

export type MyComponentProps = ExtractPublicPropTypesFromFactory<typeof makeProps>
// { color?: string, disabled?: boolean }

ExtractPublicPropTypesFromUnknownFactory<T>

Purpose: Similar to ExtractPublicPropTypesFromFactory but works with unknown factory function signatures (more permissive type constraint).

Use case:

Type extraction in scenarios where the factory function type is not precisely known at compile time.

Real-world usage patterns

The library uses these factories extensively:

  • ResourcefulRecord: propsAndEmitsFactory for all lifecycle events (saving, creating, updating, deleting, etc.) with automatic handler prop generation.
  • TimezonePicker: propsAndEmitsFactory for update:modelValue and selection events.
  • Sortable: emitsFactory for drag/drop events (start, end, add, remove, etc.).
  • Field components: propsAndDefaultsFactory to share defaults across field variants (RIntegerField, RUnsignedIntegerField, etc.).

Summary

Use these factories to build Vue components with:

  • Type-safe props and emits with automatic handler prop generation.
  • Flexible naming conventions (camelCase and kebab-case support).
  • Default value extraction for programmatic access and sharing.
  • Runtime validation preserved alongside TypeScript type safety.