or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdcore-components.mdindex.mdmethod-lifecycle-decorators.mdproperty-decorators.md

advanced-features.mddocs/

0

# Advanced Features

1

2

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

3

4

## Capabilities

5

6

### Mixins Function

7

8

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

9

10

```typescript { .api }

11

/**

12

* Creates a mixin class from multiple Vue component classes

13

* @param conses - Array of component constructor classes to mix

14

* @returns Mixed class with combined functionality

15

*/

16

function mixins<T extends VueCons[]>(...conses: T): MixedClass<T>;

17

18

type MixedClass<Mixins extends VueCons[], Base extends VueCons = VueCons> =

19

Mixins extends [infer T extends VueCons, ...infer E extends VueCons[]] ?

20

MixedClass<E, VueCons<InstanceType<Base> & InstanceType<T>, MergeIdentityType<InstanceType<T>[typeof IdentitySymbol], InstanceType<Base>[typeof IdentitySymbol]>>> :

21

Base;

22

```

23

24

**Usage Examples:**

25

26

```typescript

27

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

28

import { ref } from "vue";

29

30

// Define base mixins

31

@Component

32

class LoggingMixin {

33

log(message: string) {

34

console.log(`[${this.constructor.name}] ${message}`);

35

}

36

37

created() {

38

this.log("Component created");

39

}

40

}

41

42

@Component

43

class CounterMixin {

44

@Setup(() => ref(0))

45

count!: number;

46

47

increment() {

48

this.count++;

49

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

50

}

51

52

decrement() {

53

this.count--;

54

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

55

}

56

}

57

58

@Component

59

class TimestampMixin {

60

@Setup(() => ref(Date.now()))

61

createdAt!: number;

62

63

getAge() {

64

return Date.now() - this.createdAt;

65

}

66

}

67

68

// Combine mixins

69

@Component

70

class MixedComponent extends mixins(LoggingMixin, CounterMixin, TimestampMixin) {

71

@Prop({ type: String, required: true })

72

title!: string;

73

74

mounted() {

75

this.log(`Mounted with title: ${this.title}`);

76

this.log(`Component age: ${this.getAge()}ms`);

77

}

78

79

handleClick() {

80

this.increment();

81

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

82

}

83

}

84

85

// Multiple inheritance chains

86

@Component

87

class BaseFeature {

88

baseMethod() {

89

return "from base";

90

}

91

}

92

93

@Component

94

class FeatureA extends BaseFeature {

95

featureAMethod() {

96

return "from feature A";

97

}

98

}

99

100

@Component

101

class FeatureB extends BaseFeature {

102

featureBMethod() {

103

return "from feature B";

104

}

105

}

106

107

@Component

108

class CombinedFeatures extends mixins(FeatureA, FeatureB) {

109

combinedMethod() {

110

return `${this.baseMethod()}, ${this.featureAMethod()}, ${this.featureBMethod()}`;

111

}

112

}

113

```

114

115

### Custom Decorator Factory

116

117

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

118

119

```typescript { .api }

120

/**

121

* Factory for creating custom decorators

122

* @param creator - Function that modifies the Vue component option

123

* @param options - Optional configuration for the decorator

124

* @returns Custom decorator function

125

*/

126

function createDecorator(

127

creator: Creator,

128

options?: { preserve?: boolean }

129

): PropertyDecorator;

130

131

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

132

133

interface CustomDecoratorRecord {

134

key: string;

135

creator: Creator;

136

preserve: boolean;

137

}

138

```

139

140

**Usage Examples:**

141

142

```typescript

143

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

144

import { ref } from "vue";

145

146

// Create custom validation decorator

147

const Validate = createDecorator((option: any, key: string) => {

148

// Add validation logic to component options

149

option.methods = option.methods || {};

150

option.methods[`validate${key.charAt(0).toUpperCase() + key.slice(1)}`] = function() {

151

// Custom validation logic

152

return this[key] !== null && this[key] !== undefined;

153

};

154

});

155

156

// Create custom logging decorator

157

const AutoLog = createDecorator((option: any, key: string) => {

158

option.watch = option.watch || {};

159

option.watch[key] = {

160

handler(newVal: any, oldVal: any) {

161

console.log(`${key} changed from`, oldVal, 'to', newVal);

162

},

163

immediate: false

164

};

165

});

166

167

// Create debounced decorator

168

const Debounced = (delay: number) => createDecorator((option: any, key: string) => {

169

const originalMethod = option.methods?.[key];

170

if (originalMethod) {

171

let timeoutId: any;

172

option.methods[key] = function(...args: any[]) {

173

clearTimeout(timeoutId);

174

timeoutId = setTimeout(() => {

175

originalMethod.apply(this, args);

176

}, delay);

177

};

178

}

179

});

180

181

// Usage of custom decorators

182

@Component

183

class CustomDecoratorComponent {

184

@Validate()

185

@AutoLog()

186

@Setup(() => ref(""))

187

username!: string;

188

189

@AutoLog()

190

@Setup(() => ref(0))

191

score!: number;

192

193

@Debounced(300)

194

search() {

195

console.log("Searching for:", this.username);

196

}

197

198

mounted() {

199

// Validation method was auto-generated

200

console.log("Username valid:", this.validateUsername());

201

}

202

}

203

204

// Persistent decorator that preserves original behavior

205

const Trackable = createDecorator((option: any, key: string) => {

206

// Track property access

207

option.created = option.created || [];

208

if (!Array.isArray(option.created)) {

209

option.created = [option.created];

210

}

211

212

option.created.push(function() {

213

console.log(`Trackable property "${key}" initialized`);

214

});

215

}, { preserve: true });

216

217

@Component

218

class TrackableComponent {

219

@Trackable()

220

@Setup(() => ref("tracked value"))

221

trackedProp!: string;

222

}

223

```

224

225

### TypeScript JSX Support

226

227

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

228

229

```typescript { .api }

230

/**

231

* Generic function for TypeScript JSX/TSX type support

232

* @returns Type-enhanced component decorator

233

*/

234

function TSX<

235

Properties extends IdentityType['props'] = {},

236

Events extends IdentityType['events'] = {},

237

IT extends IdentityType = { props: Properties; events: Events }

238

>(): <C extends VueCons>(cons: C) => VueCons<InstanceType<C>, MergeIdentityType<IT, InstanceType<C>[typeof IdentitySymbol]>>;

239

240

interface IdentityType {

241

props: Record<string, any>;

242

events: Record<string, any>;

243

}

244

```

245

246

**Usage Examples:**

247

248

```typescript

249

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

250

import { h, VNode } from "vue";

251

252

// Define types for props and events

253

interface ButtonProps {

254

variant: 'primary' | 'secondary';

255

disabled?: boolean;

256

size?: 'small' | 'medium' | 'large';

257

}

258

259

interface ButtonEvents {

260

click: (event: MouseEvent) => void;

261

focus: () => void;

262

blur: () => void;

263

}

264

265

// Apply TSX typing

266

@TSX<ButtonProps, ButtonEvents>()

267

@Component

268

class TypedButton {

269

@Prop({ type: String, required: true, validator: (v: string) => ['primary', 'secondary'].includes(v) })

270

variant!: 'primary' | 'secondary';

271

272

@Prop({ type: Boolean, default: false })

273

disabled!: boolean;

274

275

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

276

size!: 'small' | 'medium' | 'large';

277

278

@Emit('click')

279

handleClick(event: MouseEvent) {

280

if (!this.disabled) {

281

return event;

282

}

283

}

284

285

@Emit('focus')

286

handleFocus() {

287

console.log('Button focused');

288

}

289

290

@Emit('blur')

291

handleBlur() {

292

console.log('Button blurred');

293

}

294

295

render(): VNode {

296

return h('button', {

297

class: [

298

'btn',

299

`btn--${this.variant}`,

300

`btn--${this.size}`,

301

{ 'btn--disabled': this.disabled }

302

],

303

disabled: this.disabled,

304

onClick: this.handleClick,

305

onFocus: this.handleFocus,

306

onBlur: this.handleBlur

307

}, this.$slots.default?.());

308

}

309

}

310

311

// Complex component with nested types

312

interface FormData {

313

username: string;

314

email: string;

315

age: number;

316

}

317

318

interface FormProps {

319

initialData?: Partial<FormData>;

320

readonly?: boolean;

321

}

322

323

interface FormEvents {

324

submit: (data: FormData) => void;

325

change: (field: keyof FormData, value: any) => void;

326

reset: () => void;

327

}

328

329

@TSX<FormProps, FormEvents>()

330

@Component

331

class TypedForm {

332

@Prop({ type: Object, default: () => ({}) })

333

initialData!: Partial<FormData>;

334

335

@Prop({ type: Boolean, default: false })

336

readonly!: boolean;

337

338

private formData: FormData = {

339

username: '',

340

email: '',

341

age: 0,

342

...this.initialData

343

};

344

345

@Emit('submit')

346

handleSubmit() {

347

return { ...this.formData };

348

}

349

350

@Emit('change')

351

handleFieldChange(field: keyof FormData, value: any) {

352

this.formData[field] = value as any;

353

return { field, value };

354

}

355

356

@Emit('reset')

357

handleReset() {

358

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

359

}

360

361

render(): VNode {

362

return h('form', {

363

onSubmit: (e: Event) => {

364

e.preventDefault();

365

this.handleSubmit();

366

}

367

}, [

368

h('input', {

369

value: this.formData.username,

370

disabled: this.readonly,

371

onInput: (e: Event) => this.handleFieldChange('username', (e.target as HTMLInputElement).value)

372

}),

373

h('input', {

374

type: 'email',

375

value: this.formData.email,

376

disabled: this.readonly,

377

onInput: (e: Event) => this.handleFieldChange('email', (e.target as HTMLInputElement).value)

378

}),

379

h('input', {

380

type: 'number',

381

value: this.formData.age,

382

disabled: this.readonly,

383

onInput: (e: Event) => this.handleFieldChange('age', parseInt((e.target as HTMLInputElement).value))

384

}),

385

h('button', { type: 'submit' }, 'Submit'),

386

h('button', { type: 'button', onClick: this.handleReset }, 'Reset')

387

]);

388

}

389

}

390

```

391

392

## Advanced Type System

393

394

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

395

396

```typescript { .api }

397

// Core identity system for type preservation

398

interface Identity<IT extends IdentityType = { props: {}, events: {} }> {

399

readonly [IdentitySymbol]: IT;

400

}

401

402

// Type merging utility for mixins

403

type MergeIdentityType<A extends IdentityType, B extends IdentityType> = {

404

props: A['props'] & B['props'];

405

events: A['events'] & B['events'];

406

};

407

408

// Vue constructor with identity preservation

409

type VueCons<

410

RawInstance extends Identity = Identity,

411

IT extends IdentityType = { props: {}, events: {} }

412

> = {

413

new(): ComponentPublicInstance<IT['props'] & EventHandlers<IT['events']>> &

414

Identity<IT> &

415

Omit<RawInstance, typeof IdentitySymbol>;

416

};

417

418

// Event handler type generation

419

type EventHandlers<Events extends Record<string, any>> = {

420

[K in keyof Events as `on${Capitalize<K & string>}`]?:

421

Events[K] extends Function ? Events[K] : (param: Events[K]) => any;

422

};

423

```

424

425

**Complete Advanced Example:**

426

427

```typescript

428

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

429

import { ref, computed } from "vue";

430

431

// Custom decorator for automatic validation

432

const AutoValidate = createDecorator((option: any, key: string) => {

433

option.computed = option.computed || {};

434

option.computed[`${key}Valid`] = function() {

435

const value = this[key];

436

return value !== null && value !== undefined && value !== '';

437

};

438

});

439

440

// Logging mixin

441

@Component

442

class LoggingMixin {

443

log(level: 'info' | 'warn' | 'error', message: string) {

444

console[level](`[${this.constructor.name}] ${message}`);

445

}

446

}

447

448

// Validation mixin

449

@Component

450

class ValidationMixin {

451

@Setup(() => ref<string[]>([]))

452

errors!: string[];

453

454

addError(field: string, message: string) {

455

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

456

}

457

458

clearErrors() {

459

this.errors.splice(0);

460

}

461

462

get isValid() {

463

return this.errors.length === 0;

464

}

465

}

466

467

// Complex component with all advanced features

468

interface ContactFormProps {

469

mode: 'create' | 'edit';

470

initialData?: { name: string; email: string; phone: string };

471

}

472

473

interface ContactFormEvents {

474

save: (data: { name: string; email: string; phone: string }) => void;

475

cancel: () => void;

476

validate: (isValid: boolean) => void;

477

}

478

479

@TSX<ContactFormProps, ContactFormEvents>()

480

@Component

481

class AdvancedContactForm extends mixins(LoggingMixin, ValidationMixin) {

482

@Prop({ type: String, required: true })

483

mode!: 'create' | 'edit';

484

485

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

486

initialData!: { name: string; email: string; phone: string };

487

488

@AutoValidate()

489

@Setup(() => ref(''))

490

name!: string;

491

492

@AutoValidate()

493

@Setup(() => ref(''))

494

email!: string;

495

496

@AutoValidate()

497

@Setup(() => ref(''))

498

phone!: string;

499

500

@Setup(() => computed(() => this.nameValid && this.emailValid && this.phoneValid && this.isValid))

501

formValid!: boolean;

502

503

created() {

504

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

505

Object.assign(this, this.initialData);

506

}

507

508

@Emit('save')

509

handleSave() {

510

this.clearErrors();

511

this.validateForm();

512

513

if (this.formValid) {

514

this.log('info', 'Form saved successfully');

515

return { name: this.name, email: this.email, phone: this.phone };

516

} else {

517

this.log('warn', 'Form validation failed');

518

return null;

519

}

520

}

521

522

@Emit('cancel')

523

handleCancel() {

524

this.log('info', 'Form cancelled');

525

}

526

527

@Emit('validate')

528

validateForm() {

529

this.clearErrors();

530

531

if (!this.name) this.addError('name', 'Name is required');

532

if (!this.email) this.addError('email', 'Email is required');

533

if (!this.phone) this.addError('phone', 'Phone is required');

534

535

return this.formValid;

536

}

537

538

render() {

539

return h('form', { class: 'contact-form' }, [

540

h('h2', this.mode === 'create' ? 'Create Contact' : 'Edit Contact'),

541

h('input', {

542

placeholder: 'Name',

543

value: this.name,

544

onInput: (e: Event) => this.name = (e.target as HTMLInputElement).value

545

}),

546

h('input', {

547

type: 'email',

548

placeholder: 'Email',

549

value: this.email,

550

onInput: (e: Event) => this.email = (e.target as HTMLInputElement).value

551

}),

552

h('input', {

553

type: 'tel',

554

placeholder: 'Phone',

555

value: this.phone,

556

onInput: (e: Event) => this.phone = (e.target as HTMLInputElement).value

557

}),

558

this.errors.length > 0 && h('ul', { class: 'errors' },

559

this.errors.map(error => h('li', error))

560

),

561

h('div', { class: 'buttons' }, [

562

h('button', { type: 'button', onClick: this.handleSave }, 'Save'),

563

h('button', { type: 'button', onClick: this.handleCancel }, 'Cancel')

564

])

565

]);

566

}

567

}

568

```