or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-configuration.mdbasic-selection.mdcustom-rendering.mdgrouped-options.mdindex.mdsearch-filtering.mdtagging-mode.md

tagging-mode.mddocs/

0

# Tagging Mode

1

2

Advanced tagging functionality allowing users to create new options dynamically through text input.

3

4

## Capabilities

5

6

### Basic Tagging

7

8

Enable tagging mode to allow users to create new options by typing and pressing Enter.

9

10

```typescript { .api }

11

/**

12

* Basic tagging configuration props

13

*/

14

interface BasicTaggingProps {

15

/** Enable tagging functionality (default: false) */

16

taggable?: boolean;

17

18

/** Placeholder text shown when highlighting tag creation (default: 'Press enter to create a tag') */

19

tagPlaceholder?: string;

20

21

/** Position of tag creation option ('top' | 'bottom', default: 'top') */

22

tagPosition?: 'top' | 'bottom';

23

}

24

```

25

26

**Usage Example:**

27

28

```vue

29

<template>

30

<VueMultiselect

31

v-model="selectedTags"

32

:options="availableTags"

33

:multiple="true"

34

:taggable="true"

35

:searchable="true"

36

@tag="addTag"

37

tag-placeholder="Add this as new tag"

38

placeholder="Select or create tags">

39

</VueMultiselect>

40

</template>

41

42

<script>

43

export default {

44

data() {

45

return {

46

selectedTags: [],

47

availableTags: ['Vue.js', 'React', 'Angular']

48

}

49

},

50

methods: {

51

addTag(newTag) {

52

const tag = {

53

name: newTag,

54

code: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))

55

};

56

this.availableTags.push(tag);

57

this.selectedTags.push(tag);

58

}

59

}

60

}

61

</script>

62

```

63

64

### Tagging Events

65

66

Events emitted during tag creation operations.

67

68

```typescript { .api }

69

/**

70

* Tagging-related events

71

*/

72

interface TaggingEvents {

73

/**

74

* Emitted when user creates a new tag

75

* @param searchQuery - Text entered by user for new tag

76

* @param id - Component identifier

77

*/

78

'@tag': (searchQuery: string, id: string | number) => void;

79

}

80

```

81

82

### Tag Creation Logic

83

84

Control how new tags are created and integrated with existing options.

85

86

```vue

87

<template>

88

<VueMultiselect

89

v-model="selectedSkills"

90

:options="skills"

91

:multiple="true"

92

:taggable="true"

93

:searchable="true"

94

@tag="createSkill"

95

label="name"

96

track-by="id"

97

tag-placeholder="Create new skill"

98

placeholder="Select or add skills">

99

</VueMultiselect>

100

</template>

101

102

<script>

103

export default {

104

data() {

105

return {

106

selectedSkills: [],

107

skills: [

108

{ id: 1, name: 'JavaScript', category: 'Programming' },

109

{ id: 2, name: 'Design', category: 'Creative' }

110

],

111

nextId: 3

112

}

113

},

114

methods: {

115

createSkill(name) {

116

// Validate tag name

117

if (name.length < 2) {

118

alert('Skill name must be at least 2 characters');

119

return;

120

}

121

122

// Check for duplicates

123

const exists = this.skills.some(skill =>

124

skill.name.toLowerCase() === name.toLowerCase()

125

);

126

127

if (exists) {

128

alert('This skill already exists');

129

return;

130

}

131

132

// Create new skill

133

const newSkill = {

134

id: this.nextId++,

135

name: name,

136

category: 'Custom'

137

};

138

139

// Add to options and select it

140

this.skills.push(newSkill);

141

this.selectedSkills.push(newSkill);

142

}

143

}

144

}

145

</script>

146

```

147

148

### Tag Positioning

149

150

Control where the tag creation option appears in the dropdown.

151

152

```typescript { .api }

153

/**

154

* Tag positioning configuration

155

*/

156

interface TagPositioningProps {

157

/**

158

* Position of tag creation option in dropdown

159

* 'top' - Show above search results (default)

160

* 'bottom' - Show below search results

161

*/

162

tagPosition?: 'top' | 'bottom';

163

}

164

```

165

166

**Usage Example:**

167

168

```vue

169

<template>

170

<VueMultiselect

171

v-model="selectedCategories"

172

:options="categories"

173

:multiple="true"

174

:taggable="true"

175

tag-position="bottom"

176

tag-placeholder="Create new category"

177

@tag="addCategory">

178

</VueMultiselect>

179

</template>

180

181

<script>

182

export default {

183

methods: {

184

addCategory(categoryName) {

185

// Add category to bottom of list

186

this.categories.push(categoryName);

187

this.selectedCategories.push(categoryName);

188

}

189

}

190

}

191

</script>

192

```

193

194

### Advanced Tag Creation

195

196

Implement complex tag creation with validation, transformation, and async operations.

197

198

```vue

199

<template>

200

<VueMultiselect

201

v-model="selectedTags"

202

:options="availableTags"

203

:multiple="true"

204

:taggable="true"

205

:loading="isCreatingTag"

206

@tag="createTagAsync"

207

label="name"

208

track-by="id"

209

tag-placeholder="Create and save new tag"

210

placeholder="Select existing or create new tags">

211

212

<template #tag="{ option, remove }">

213

<span class="custom-tag">

214

<span>{{ option.name }}</span>

215

<span v-if="option.isNew" class="tag-new-indicator">NEW</span>

216

<button @click="remove(option)" class="tag-remove">×</button>

217

</span>

218

</template>

219

</VueMultiselect>

220

</template>

221

222

<script>

223

export default {

224

data() {

225

return {

226

selectedTags: [],

227

availableTags: [

228

{ id: 1, name: 'JavaScript', isNew: false },

229

{ id: 2, name: 'Vue.js', isNew: false }

230

],

231

isCreatingTag: false

232

}

233

},

234

methods: {

235

async createTagAsync(tagName) {

236

// Validate tag name

237

if (!this.validateTagName(tagName)) {

238

return;

239

}

240

241

this.isCreatingTag = true;

242

243

try {

244

// Transform tag name

245

const normalizedName = this.normalizeTagName(tagName);

246

247

// Check if tag exists (case-insensitive)

248

const existingTag = this.availableTags.find(tag =>

249

tag.name.toLowerCase() === normalizedName.toLowerCase()

250

);

251

252

if (existingTag) {

253

// Select existing tag instead

254

if (!this.selectedTags.includes(existingTag)) {

255

this.selectedTags.push(existingTag);

256

}

257

return;

258

}

259

260

// Create new tag via API

261

const newTag = await this.saveTagToServer(normalizedName);

262

263

// Add to local state

264

this.availableTags.push(newTag);

265

this.selectedTags.push(newTag);

266

267

} catch (error) {

268

console.error('Failed to create tag:', error);

269

alert('Failed to create tag. Please try again.');

270

} finally {

271

this.isCreatingTag = false;

272

}

273

},

274

275

validateTagName(name) {

276

if (!name || name.trim().length < 2) {

277

alert('Tag name must be at least 2 characters');

278

return false;

279

}

280

281

if (name.length > 50) {

282

alert('Tag name must be less than 50 characters');

283

return false;

284

}

285

286

if (!/^[a-zA-Z0-9\s\-.]+$/.test(name)) {

287

alert('Tag name contains invalid characters');

288

return false;

289

}

290

291

return true;

292

},

293

294

normalizeTagName(name) {

295

return name.trim()

296

.replace(/\s+/g, ' ') // Replace multiple spaces with single space

297

.toLowerCase()

298

.replace(/^\w/, c => c.toUpperCase()); // Capitalize first letter

299

},

300

301

async saveTagToServer(name) {

302

const response = await fetch('/api/tags', {

303

method: 'POST',

304

headers: {

305

'Content-Type': 'application/json'

306

},

307

body: JSON.stringify({ name })

308

});

309

310

if (!response.ok) {

311

throw new Error('Failed to save tag');

312

}

313

314

const tag = await response.json();

315

return {

316

...tag,

317

isNew: true

318

};

319

}

320

}

321

}

322

</script>

323

324

<style>

325

.custom-tag {

326

display: inline-flex;

327

align-items: center;

328

gap: 4px;

329

padding: 4px 8px;

330

background: #e3f2fd;

331

border-radius: 4px;

332

}

333

334

.tag-new-indicator {

335

font-size: 10px;

336

background: #4caf50;

337

color: white;

338

padding: 1px 4px;

339

border-radius: 2px;

340

}

341

342

.tag-remove {

343

background: none;

344

border: none;

345

cursor: pointer;

346

font-size: 14px;

347

color: #666;

348

}

349

</style>

350

```

351

352

### Tag Management

353

354

Methods for managing tags programmatically.

355

356

```typescript { .api }

357

/**

358

* Tag management methods

359

*/

360

interface TagManagementMethods {

361

/** Check if a tag with given text already exists */

362

isExistingOption(query: string): boolean;

363

364

/** Add a new tag programmatically */

365

addTag(tagData: any): void;

366

367

/** Remove a tag from available options */

368

removeTag(tag: any): void;

369

370

/** Get all currently selected tags */

371

getSelectedTags(): any[];

372

}

373

```

374

375

### Tagging with Objects

376

377

Use tagging with object-based options for more complex tag structures.

378

379

```vue

380

<template>

381

<VueMultiselect

382

v-model="selectedTopics"

383

:options="topics"

384

:multiple="true"

385

:taggable="true"

386

@tag="createTopic"

387

label="title"

388

track-by="slug"

389

tag-placeholder="Create new topic">

390

</VueMultiselect>

391

</template>

392

393

<script>

394

export default {

395

data() {

396

return {

397

selectedTopics: [],

398

topics: [

399

{ slug: 'javascript', title: 'JavaScript', color: '#f7df1e' },

400

{ slug: 'vue', title: 'Vue.js', color: '#4fc08d' }

401

]

402

}

403

},

404

methods: {

405

createTopic(title) {

406

const slug = title.toLowerCase()

407

.replace(/[^a-z0-9]+/g, '-')

408

.replace(/(^-|-$)/g, '');

409

410

const newTopic = {

411

slug: slug,

412

title: title,

413

color: this.getRandomColor(),

414

isCustom: true

415

};

416

417

this.topics.push(newTopic);

418

this.selectedTopics.push(newTopic);

419

},

420

421

getRandomColor() {

422

const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57'];

423

return colors[Math.floor(Math.random() * colors.length)];

424

}

425

}

426

}

427

</script>

428

```

429

430

### Keyboard Shortcuts

431

432

Tagging supports standard keyboard shortcuts for tag creation.

433

434

```typescript { .api }

435

/**

436

* Keyboard shortcuts for tagging

437

*/

438

interface TaggingKeyboardShortcuts {

439

/** Enter key - Create tag from current search query */

440

'Enter': void;

441

442

/** Tab key - Create tag and continue to next field */

443

'Tab': void;

444

445

/** Escape key - Cancel tag creation and close dropdown */

446

'Escape': void;

447

}

448

```

449

450

**Usage Notes:**

451

452

- Press `Enter` while typing to create a new tag

453

- Press `Tab` to create a tag and move focus to next form element

454

- Press `Escape` to cancel tag creation and close dropdown

455

- Tags are created from the current search input value

456

- Tag creation only works when `taggable` prop is true