CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vue-facing-decorator

Vue typescript class and decorator based component system with support for ES class inheritance and Vue 3 composition API integration.

Pending
Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

Mixins, custom decorators, TypeScript JSX support, and utility functions for advanced Vue component composition.

Capabilities

Mixins Function

Function for creating mixins with multiple Vue component classes, supporting full ES class inheritance.

/**
 * Creates a mixin class from multiple Vue component classes
 * @param conses - Array of component constructor classes to mix
 * @returns Mixed class with combined functionality
 */
function mixins<T extends VueCons[]>(...conses: T): MixedClass<T>;

type MixedClass<Mixins extends VueCons[], Base extends VueCons = VueCons> =
  Mixins extends [infer T extends VueCons, ...infer E extends VueCons[]] ?
    MixedClass<E, VueCons<InstanceType<Base> & InstanceType<T>, MergeIdentityType<InstanceType<T>[typeof IdentitySymbol], InstanceType<Base>[typeof IdentitySymbol]>>> :
    Base;

Usage Examples:

import { Component, mixins, Setup, Prop } from "vue-facing-decorator";
import { ref } from "vue";

// Define base mixins
@Component
class LoggingMixin {
  log(message: string) {
    console.log(`[${this.constructor.name}] ${message}`);
  }

  created() {
    this.log("Component created");
  }
}

@Component
class CounterMixin {
  @Setup(() => ref(0))
  count!: number;

  increment() {
    this.count++;
    this.log?.(`Count incremented to ${this.count}`);
  }

  decrement() {
    this.count--;
    this.log?.(`Count decremented to ${this.count}`);
  }
}

@Component
class TimestampMixin {
  @Setup(() => ref(Date.now()))
  createdAt!: number;

  getAge() {
    return Date.now() - this.createdAt;
  }
}

// Combine mixins
@Component
class MixedComponent extends mixins(LoggingMixin, CounterMixin, TimestampMixin) {
  @Prop({ type: String, required: true })
  title!: string;

  mounted() {
    this.log(`Mounted with title: ${this.title}`);
    this.log(`Component age: ${this.getAge()}ms`);
  }

  handleClick() {
    this.increment();
    this.log(`Click handled, age: ${this.getAge()}ms`);
  }
}

// Multiple inheritance chains
@Component
class BaseFeature {
  baseMethod() {
    return "from base";
  }
}

@Component
class FeatureA extends BaseFeature {
  featureAMethod() {
    return "from feature A";
  }
}

@Component
class FeatureB extends BaseFeature {
  featureBMethod() {
    return "from feature B";
  }
}

@Component
class CombinedFeatures extends mixins(FeatureA, FeatureB) {
  combinedMethod() {
    return `${this.baseMethod()}, ${this.featureAMethod()}, ${this.featureBMethod()}`;
  }
}

Custom Decorator Factory

Factory function for creating custom decorators with full integration into the Vue component system.

/**
 * Factory for creating custom decorators
 * @param creator - Function that modifies the Vue component option
 * @param options - Optional configuration for the decorator
 * @returns Custom decorator function
 */
function createDecorator(
  creator: Creator, 
  options?: { preserve?: boolean }
): PropertyDecorator;

type Creator = (options: any, key: string) => void;

interface CustomDecoratorRecord {
  key: string;
  creator: Creator;
  preserve: boolean;
}

Usage Examples:

import { Component, createDecorator, Setup } from "vue-facing-decorator";
import { ref } from "vue";

// Create custom validation decorator
const Validate = createDecorator((option: any, key: string) => {
  // Add validation logic to component options
  option.methods = option.methods || {};
  option.methods[`validate${key.charAt(0).toUpperCase() + key.slice(1)}`] = function() {
    // Custom validation logic
    return this[key] !== null && this[key] !== undefined;
  };
});

// Create custom logging decorator
const AutoLog = createDecorator((option: any, key: string) => {
  option.watch = option.watch || {};
  option.watch[key] = {
    handler(newVal: any, oldVal: any) {
      console.log(`${key} changed from`, oldVal, 'to', newVal);
    },
    immediate: false
  };
});

// Create debounced decorator
const Debounced = (delay: number) => createDecorator((option: any, key: string) => {
  const originalMethod = option.methods?.[key];
  if (originalMethod) {
    let timeoutId: any;
    option.methods[key] = function(...args: any[]) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        originalMethod.apply(this, args);
      }, delay);
    };
  }
});

// Usage of custom decorators
@Component
class CustomDecoratorComponent {
  @Validate()
  @AutoLog()
  @Setup(() => ref(""))
  username!: string;

  @AutoLog()
  @Setup(() => ref(0))
  score!: number;

  @Debounced(300)
  search() {
    console.log("Searching for:", this.username);
  }

  mounted() {
    // Validation method was auto-generated
    console.log("Username valid:", this.validateUsername());
  }
}

// Persistent decorator that preserves original behavior
const Trackable = createDecorator((option: any, key: string) => {
  // Track property access
  option.created = option.created || [];
  if (!Array.isArray(option.created)) {
    option.created = [option.created];
  }
  
  option.created.push(function() {
    console.log(`Trackable property "${key}" initialized`);
  });
}, { preserve: true });

@Component
class TrackableComponent {
  @Trackable()
  @Setup(() => ref("tracked value"))
  trackedProp!: string;
}

TypeScript JSX Support

Generic function providing TypeScript support for JSX/TSX components with full type inference.

/**
 * Generic function for TypeScript JSX/TSX type support
 * @returns Type-enhanced component decorator
 */
function TSX<
  Properties extends IdentityType['props'] = {}, 
  Events extends IdentityType['events'] = {}, 
  IT extends IdentityType = { props: Properties; events: Events }
>(): <C extends VueCons>(cons: C) => VueCons<InstanceType<C>, MergeIdentityType<IT, InstanceType<C>[typeof IdentitySymbol]>>;

interface IdentityType {
  props: Record<string, any>;
  events: Record<string, any>;
}

Usage Examples:

import { Component, TSX, Prop, Emit } from "vue-facing-decorator";
import { h, VNode } from "vue";

// Define types for props and events
interface ButtonProps {
  variant: 'primary' | 'secondary';
  disabled?: boolean;
  size?: 'small' | 'medium' | 'large';
}

interface ButtonEvents {
  click: (event: MouseEvent) => void;
  focus: () => void;
  blur: () => void;
}

// Apply TSX typing
@TSX<ButtonProps, ButtonEvents>()
@Component
class TypedButton {
  @Prop({ type: String, required: true, validator: (v: string) => ['primary', 'secondary'].includes(v) })
  variant!: 'primary' | 'secondary';

  @Prop({ type: Boolean, default: false })
  disabled!: boolean;

  @Prop({ type: String, default: 'medium', validator: (v: string) => ['small', 'medium', 'large'].includes(v) })
  size!: 'small' | 'medium' | 'large';

  @Emit('click')
  handleClick(event: MouseEvent) {
    if (!this.disabled) {
      return event;
    }
  }

  @Emit('focus')
  handleFocus() {
    console.log('Button focused');
  }

  @Emit('blur')
  handleBlur() {
    console.log('Button blurred');
  }

  render(): VNode {
    return h('button', {
      class: [
        'btn',
        `btn--${this.variant}`,
        `btn--${this.size}`,
        { 'btn--disabled': this.disabled }
      ],
      disabled: this.disabled,
      onClick: this.handleClick,
      onFocus: this.handleFocus,
      onBlur: this.handleBlur
    }, this.$slots.default?.());
  }
}

// Complex component with nested types
interface FormData {
  username: string;
  email: string;
  age: number;
}

interface FormProps {
  initialData?: Partial<FormData>;
  readonly?: boolean;
}

interface FormEvents {
  submit: (data: FormData) => void;
  change: (field: keyof FormData, value: any) => void;
  reset: () => void;
}

@TSX<FormProps, FormEvents>()
@Component
class TypedForm {
  @Prop({ type: Object, default: () => ({}) })
  initialData!: Partial<FormData>;

  @Prop({ type: Boolean, default: false })
  readonly!: boolean;

  private formData: FormData = {
    username: '',
    email: '',
    age: 0,
    ...this.initialData
  };

  @Emit('submit')
  handleSubmit() {
    return { ...this.formData };
  }

  @Emit('change')
  handleFieldChange(field: keyof FormData, value: any) {
    this.formData[field] = value as any;
    return { field, value };
  }

  @Emit('reset')
  handleReset() {
    this.formData = { username: '', email: '', age: 0, ...this.initialData };
  }

  render(): VNode {
    return h('form', {
      onSubmit: (e: Event) => {
        e.preventDefault();
        this.handleSubmit();
      }
    }, [
      h('input', {
        value: this.formData.username,
        disabled: this.readonly,
        onInput: (e: Event) => this.handleFieldChange('username', (e.target as HTMLInputElement).value)
      }),
      h('input', {
        type: 'email',
        value: this.formData.email,
        disabled: this.readonly,
        onInput: (e: Event) => this.handleFieldChange('email', (e.target as HTMLInputElement).value)
      }),
      h('input', {
        type: 'number',
        value: this.formData.age,
        disabled: this.readonly,
        onInput: (e: Event) => this.handleFieldChange('age', parseInt((e.target as HTMLInputElement).value))
      }),
      h('button', { type: 'submit' }, 'Submit'),
      h('button', { type: 'button', onClick: this.handleReset }, 'Reset')
    ]);
  }
}

Advanced Type System

The advanced features leverage a sophisticated type system for full TypeScript integration:

// Core identity system for type preservation
interface Identity<IT extends IdentityType = { props: {}, events: {} }> {
  readonly [IdentitySymbol]: IT;
}

// Type merging utility for mixins
type MergeIdentityType<A extends IdentityType, B extends IdentityType> = {
  props: A['props'] & B['props'];
  events: A['events'] & B['events'];
};

// Vue constructor with identity preservation
type VueCons<
  RawInstance extends Identity = Identity, 
  IT extends IdentityType = { props: {}, events: {} }
> = {
  new(): ComponentPublicInstance<IT['props'] & EventHandlers<IT['events']>> & 
         Identity<IT> & 
         Omit<RawInstance, typeof IdentitySymbol>;
};

// Event handler type generation
type EventHandlers<Events extends Record<string, any>> = {
  [K in keyof Events as `on${Capitalize<K & string>}`]?: 
    Events[K] extends Function ? Events[K] : (param: Events[K]) => any;
};

Complete Advanced Example:

import { Component, mixins, createDecorator, TSX, Setup, Prop, Emit } from "vue-facing-decorator";
import { ref, computed } from "vue";

// Custom decorator for automatic validation
const AutoValidate = createDecorator((option: any, key: string) => {
  option.computed = option.computed || {};
  option.computed[`${key}Valid`] = function() {
    const value = this[key];
    return value !== null && value !== undefined && value !== '';
  };
});

// Logging mixin
@Component
class LoggingMixin {
  log(level: 'info' | 'warn' | 'error', message: string) {
    console[level](`[${this.constructor.name}] ${message}`);
  }
}

// Validation mixin
@Component
class ValidationMixin {
  @Setup(() => ref<string[]>([]))
  errors!: string[];

  addError(field: string, message: string) {
    this.errors.push(`${field}: ${message}`);
  }

  clearErrors() {
    this.errors.splice(0);
  }

  get isValid() {
    return this.errors.length === 0;
  }
}

// Complex component with all advanced features
interface ContactFormProps {
  mode: 'create' | 'edit';
  initialData?: { name: string; email: string; phone: string };
}

interface ContactFormEvents {
  save: (data: { name: string; email: string; phone: string }) => void;
  cancel: () => void;
  validate: (isValid: boolean) => void;
}

@TSX<ContactFormProps, ContactFormEvents>()
@Component
class AdvancedContactForm extends mixins(LoggingMixin, ValidationMixin) {
  @Prop({ type: String, required: true })
  mode!: 'create' | 'edit';

  @Prop({ type: Object, default: () => ({ name: '', email: '', phone: '' }) })
  initialData!: { name: string; email: string; phone: string };

  @AutoValidate()
  @Setup(() => ref(''))
  name!: string;

  @AutoValidate()
  @Setup(() => ref(''))
  email!: string;

  @AutoValidate()
  @Setup(() => ref(''))
  phone!: string;

  @Setup(() => computed(() => this.nameValid && this.emailValid && this.phoneValid && this.isValid))
  formValid!: boolean;

  created() {
    this.log('info', `Form created in ${this.mode} mode`);
    Object.assign(this, this.initialData);
  }

  @Emit('save')
  handleSave() {
    this.clearErrors();
    this.validateForm();
    
    if (this.formValid) {
      this.log('info', 'Form saved successfully');
      return { name: this.name, email: this.email, phone: this.phone };
    } else {
      this.log('warn', 'Form validation failed');
      return null;
    }
  }

  @Emit('cancel')
  handleCancel() {
    this.log('info', 'Form cancelled');
  }

  @Emit('validate')
  validateForm() {
    this.clearErrors();
    
    if (!this.name) this.addError('name', 'Name is required');
    if (!this.email) this.addError('email', 'Email is required');
    if (!this.phone) this.addError('phone', 'Phone is required');
    
    return this.formValid;
  }

  render() {
    return h('form', { class: 'contact-form' }, [
      h('h2', this.mode === 'create' ? 'Create Contact' : 'Edit Contact'),
      h('input', {
        placeholder: 'Name',
        value: this.name,
        onInput: (e: Event) => this.name = (e.target as HTMLInputElement).value
      }),
      h('input', {
        type: 'email', 
        placeholder: 'Email',
        value: this.email,
        onInput: (e: Event) => this.email = (e.target as HTMLInputElement).value
      }),
      h('input', {
        type: 'tel',
        placeholder: 'Phone', 
        value: this.phone,
        onInput: (e: Event) => this.phone = (e.target as HTMLInputElement).value
      }),
      this.errors.length > 0 && h('ul', { class: 'errors' }, 
        this.errors.map(error => h('li', error))
      ),
      h('div', { class: 'buttons' }, [
        h('button', { type: 'button', onClick: this.handleSave }, 'Save'),
        h('button', { type: 'button', onClick: this.handleCancel }, 'Cancel')
      ])
    ]);
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-vue-facing-decorator

docs

advanced-features.md

core-components.md

index.md

method-lifecycle-decorators.md

property-decorators.md

tile.json