@todovue/tv-search 1.1.3 → 1.2.0
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/README.md +41 -49
- package/dist/tv-search.cjs.js +2 -2
- package/dist/tv-search.es.js +104 -80
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -16,12 +16,11 @@ A fast, accessible, and fully customizable search interface component for Vue 3
|
|
|
16
16
|
|
|
17
17
|
> Demo: https://ui.todovue.blog/search
|
|
18
18
|
|
|
19
|
-
---
|
|
20
19
|
## Table of Contents
|
|
21
20
|
- [Features](#features)
|
|
22
21
|
- [Installation](#installation)
|
|
23
22
|
- [Quick Start (SPA)](#quick-start-spa)
|
|
24
|
-
- [Nuxt
|
|
23
|
+
- [Nuxt 4 / SSR Usage](#nuxt-4--ssr-usage)
|
|
25
24
|
- [Component Registration Options](#component-registration-options)
|
|
26
25
|
- [Props](#props)
|
|
27
26
|
- [Events](#events)
|
|
@@ -30,13 +29,11 @@ A fast, accessible, and fully customizable search interface component for Vue 3
|
|
|
30
29
|
- [Results Data Structure](#results-data-structure)
|
|
31
30
|
- [Accessibility](#accessibility)
|
|
32
31
|
- [SSR Notes](#ssr-notes)
|
|
33
|
-
- [Roadmap](#roadmap)
|
|
34
32
|
- [Development](#development)
|
|
35
33
|
- [Contributing](#contributing)
|
|
36
34
|
- [Changelog](#changelog)
|
|
37
35
|
- [License](#license)
|
|
38
36
|
|
|
39
|
-
---
|
|
40
37
|
## Features
|
|
41
38
|
- **Keyboard-first UX**: Open with `Ctrl+K` / `Cmd+K`, close with `Esc`
|
|
42
39
|
- **Real-time filtering**: Search as you type with instant results
|
|
@@ -45,11 +42,10 @@ A fast, accessible, and fully customizable search interface component for Vue 3
|
|
|
45
42
|
- **Accessible**: Built with semantic HTML and keyboard navigation
|
|
46
43
|
- **Lightweight**: Minimal dependencies, Vue 3 marked as peer dependency
|
|
47
44
|
- **SSR compatible**: Works in Nuxt 3 and other SSR frameworks
|
|
48
|
-
- **
|
|
45
|
+
- **Autofocus**: Input field receives focus automatically when opened
|
|
49
46
|
- **Click-away close**: Modal closes when clicking outside the content area
|
|
50
47
|
- **Flexible results**: Pass any array of searchable items with custom properties
|
|
51
48
|
|
|
52
|
-
---
|
|
53
49
|
## Installation
|
|
54
50
|
Using npm:
|
|
55
51
|
```bash
|
|
@@ -64,7 +60,6 @@ Using pnpm:
|
|
|
64
60
|
pnpm add @todovue/tv-search
|
|
65
61
|
```
|
|
66
62
|
|
|
67
|
-
---
|
|
68
63
|
## Quick Start (SPA)
|
|
69
64
|
Global registration (main.js / main.ts):
|
|
70
65
|
```js
|
|
@@ -124,8 +119,7 @@ function handleSearch(query) {
|
|
|
124
119
|
</template>
|
|
125
120
|
```
|
|
126
121
|
|
|
127
|
-
|
|
128
|
-
## Nuxt 3 / SSR Usage
|
|
122
|
+
## Nuxt 4 / SSR Usage
|
|
129
123
|
Create a plugin file: `plugins/tv-search.client.ts` (client-only is recommended since it uses keyboard events):
|
|
130
124
|
```ts
|
|
131
125
|
// nuxt.config.ts
|
|
@@ -165,7 +159,6 @@ import { TvSearch } from '@todovue/tv-search'
|
|
|
165
159
|
</script>
|
|
166
160
|
```
|
|
167
161
|
|
|
168
|
-
---
|
|
169
162
|
## Component Registration Options
|
|
170
163
|
| Approach | When to use |
|
|
171
164
|
|------------------------------------------------------|----------------------------------------------------|
|
|
@@ -174,14 +167,15 @@ import { TvSearch } from '@todovue/tv-search'
|
|
|
174
167
|
| Local named import `import TvSearch from '...'` | Single page usage / code splitting |
|
|
175
168
|
| Nuxt plugin `.client.ts` | SSR apps with client-side interactions |
|
|
176
169
|
|
|
177
|
-
---
|
|
178
170
|
## Props
|
|
179
|
-
| Prop
|
|
180
|
-
|
|
181
|
-
| placeholder
|
|
182
|
-
| titleButton
|
|
183
|
-
| results
|
|
184
|
-
| customStyles
|
|
171
|
+
| Prop | Type | Default | Description | Required |
|
|
172
|
+
|---------------|--------|--------------------------|---------------------------------------------------------------------------------------|----------|
|
|
173
|
+
| placeholder | String | `""` | Placeholder text for the search input field | `true` |
|
|
174
|
+
| titleButton | String | `""` | Text displayed on the search button | `true` |
|
|
175
|
+
| results | Array | `[]` | Array of searchable items (see [Results Data Structure](#results-data-structure)) | `true` |
|
|
176
|
+
| customStyles | Object | `{}` | Custom color scheme for theming (see [Customization](#customization-styles--theming)) | `false` |
|
|
177
|
+
| searchKeys | Array | `['title']` | Array of keys in result objects to search against | `false` |
|
|
178
|
+
| noResultsText | String | `"No results found for"` | Text to display when no results match the query | `false` |
|
|
185
179
|
|
|
186
180
|
### customStyles Object
|
|
187
181
|
Customize the appearance by passing a `customStyles` object with any of these properties:
|
|
@@ -193,11 +187,10 @@ Customize the appearance by passing a `customStyles` object with any of these pr
|
|
|
193
187
|
| bgButton | String | `"#EF233C"` | Background color of the search button |
|
|
194
188
|
| colorButton | String | `"#F4FAFF"` | Text color of the search button |
|
|
195
189
|
|
|
196
|
-
---
|
|
197
190
|
## Events
|
|
198
|
-
| Event | Payload Type
|
|
199
|
-
|
|
200
|
-
| search | String
|
|
191
|
+
| Event | Payload Type | Description |
|
|
192
|
+
|--------|-----------------|-------------------------------------------------------------------------------------------------|
|
|
193
|
+
| search | String / Object | Emitted when search is triggered (Enter key or button click). Returns the trimmed search query. |
|
|
201
194
|
|
|
202
195
|
Example:
|
|
203
196
|
```vue
|
|
@@ -216,7 +209,33 @@ function handleSearch(query) {
|
|
|
216
209
|
</script>
|
|
217
210
|
```
|
|
218
211
|
|
|
219
|
-
|
|
212
|
+
## Slots
|
|
213
|
+
| Slot Name | Props | Description |
|
|
214
|
+
|------------|--------------|---------------------------------------------------|
|
|
215
|
+
| item | `{ result }` | Custom rendering for each result item in the list |
|
|
216
|
+
| no-results | - | Custom content when no results are found |
|
|
217
|
+
|
|
218
|
+
### Custom Slot Example
|
|
219
|
+
```vue
|
|
220
|
+
<tv-search
|
|
221
|
+
:results="items"
|
|
222
|
+
:searchKeys="['title', 'description']"
|
|
223
|
+
>
|
|
224
|
+
<template #item="{ result }">
|
|
225
|
+
<div class="my-custom-item">
|
|
226
|
+
<h3>{{ result.title }}</h3>
|
|
227
|
+
<p>{{ result.description }}</p>
|
|
228
|
+
</div>
|
|
229
|
+
</template>
|
|
230
|
+
|
|
231
|
+
<template #no-results>
|
|
232
|
+
<div class="empty-state">
|
|
233
|
+
<p>No matches found.</p>
|
|
234
|
+
</div>
|
|
235
|
+
</template>
|
|
236
|
+
</tv-search>
|
|
237
|
+
```
|
|
238
|
+
|
|
220
239
|
## Keyboard Shortcuts
|
|
221
240
|
| Shortcut | Action |
|
|
222
241
|
|------------------------|-----------------------------------|
|
|
@@ -225,7 +244,6 @@ function handleSearch(query) {
|
|
|
225
244
|
| `Enter` | Execute search with current input |
|
|
226
245
|
| Click outside modal | Close the search modal |
|
|
227
246
|
|
|
228
|
-
---
|
|
229
247
|
## Customization (Styles / Theming)
|
|
230
248
|
You can override the default color scheme by passing a `customStyles` object:
|
|
231
249
|
|
|
@@ -288,7 +306,6 @@ const brandTheme = {
|
|
|
288
306
|
}
|
|
289
307
|
```
|
|
290
308
|
|
|
291
|
-
---
|
|
292
309
|
## Results Data Structure
|
|
293
310
|
The `results` prop expects an array of objects with the following structure:
|
|
294
311
|
|
|
@@ -324,7 +341,6 @@ const results = [
|
|
|
324
341
|
|
|
325
342
|
**Note**: The component currently filters results based on the `title` property matching the user input (case-insensitive). You can handle the `@search` event to implement custom search logic or navigation.
|
|
326
343
|
|
|
327
|
-
---
|
|
328
344
|
## Accessibility
|
|
329
345
|
- **Keyboard navigation**: Full support for `Ctrl+K`/`Cmd+K` to open, `Esc` to close, and `Enter` to search
|
|
330
346
|
- **Focus management**: Input automatically receives focus when modal opens and is selected for immediate typing
|
|
@@ -337,7 +353,6 @@ const results = [
|
|
|
337
353
|
- Ensure sufficient color contrast when using `customStyles`
|
|
338
354
|
- Consider adding `aria-label` attributes for screen reader support in future versions
|
|
339
355
|
|
|
340
|
-
---
|
|
341
356
|
## SSR Notes
|
|
342
357
|
- **Safe for SSR**: No direct DOM access (`window` / `document`) during module initialization
|
|
343
358
|
- **Event listeners**: Keyboard event listeners are registered in `onMounted` and cleaned up in `onBeforeUnmount`
|
|
@@ -347,25 +362,6 @@ const results = [
|
|
|
347
362
|
- For Vue/Vite SPA: `import '@todovue/tv-search/style.css'` in `main.ts`
|
|
348
363
|
- For Nuxt 3/4: Add `'@todovue/tv-search/style.css'` to the `css` array in `nuxt.config.ts`
|
|
349
364
|
|
|
350
|
-
---
|
|
351
|
-
## Roadmap
|
|
352
|
-
| Feature | Status |
|
|
353
|
-
|------------------------------------------|-------------|
|
|
354
|
-
| Support for result `url` navigation | Planned |
|
|
355
|
-
| Display `description` in results | Planned |
|
|
356
|
-
| Customizable search icon | Planned |
|
|
357
|
-
| Multiple keyboard shortcut options | Considering |
|
|
358
|
-
| Result categorization / grouping | Considering |
|
|
359
|
-
| Highlight matching text in results | Considering |
|
|
360
|
-
| Recent searches history | Considering |
|
|
361
|
-
| Loading state indicator | Considering |
|
|
362
|
-
| Pagination for large result sets | Considering |
|
|
363
|
-
| Fuzzy search / advanced filtering | Considering |
|
|
364
|
-
| Theming via CSS variables | Considering |
|
|
365
|
-
| TypeScript type definitions improvement | Planned |
|
|
366
|
-
| ARIA attributes enhancement | Planned |
|
|
367
|
-
|
|
368
|
-
---
|
|
369
365
|
## Development
|
|
370
366
|
Clone the repository and install dependencies:
|
|
371
367
|
```bash
|
|
@@ -391,7 +387,6 @@ yarn build:demo
|
|
|
391
387
|
|
|
392
388
|
The demo is served from Vite using `index.html` + `src/demo` examples.
|
|
393
389
|
|
|
394
|
-
---
|
|
395
390
|
## Contributing
|
|
396
391
|
Contributions are welcome! Please read our [Contributing Guidelines](https://github.com/TODOvue/tv-search/blob/main/CONTRIBUTING.md) and [Code of Conduct](https://github.com/TODOvue/tv-search/blob/main/CODE_OF_CONDUCT.md) before submitting PRs.
|
|
397
392
|
|
|
@@ -402,14 +397,11 @@ Contributions are welcome! Please read our [Contributing Guidelines](https://git
|
|
|
402
397
|
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
403
398
|
5. Open a Pull Request
|
|
404
399
|
|
|
405
|
-
---
|
|
406
400
|
## Changelog
|
|
407
401
|
See [CHANGELOG.md](https://github.com/TODOvue/tv-search/blob/main/CHANGELOG.md) for release history and version changes.
|
|
408
402
|
|
|
409
|
-
---
|
|
410
403
|
## License
|
|
411
404
|
[MIT](https://github.com/TODOvue/tv-search/blob/main/LICENSE) © TODOvue
|
|
412
405
|
|
|
413
|
-
---
|
|
414
406
|
### Attributions
|
|
415
407
|
Crafted for the TODOvue component ecosystem
|
package/dist/tv-search.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),C=require("@todovue/tv-button"),w=`<svg class="tv-icon-svg" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 129 129">
|
|
2
2
|
<g>
|
|
3
3
|
<path d="M51.6,96.7c11,0,21-3.9,28.8-10.5l35,35c0.8,0.8,1.8,1.2,2.9,1.2s2.1-0.4,2.9-1.2c1.6-1.6,1.6-4.2,0-5.8l-35-35
|
|
4
4
|
c6.5-7.8,10.5-17.9,10.5-28.8c0-24.9-20.2-45.1-45.1-45.1C26.8,6.5,6.5,26.8,6.5,51.6C6.5,76.5,26.8,96.7,51.6,96.7z
|
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
fill="currentColor"/>
|
|
7
7
|
</g>
|
|
8
8
|
</svg>
|
|
9
|
-
`,
|
|
9
|
+
`,_=(r,v)=>{const s=e.ref(""),u=e.ref(!1),c=e.ref(),y=Object.assign({"../assets/icons/search.svg":w}),h=e.computed(()=>y["../assets/icons/search.svg"]||""),i=()=>{l(!0),d()},m=(t=null)=>{if(t&&(t instanceof Event||t?.target&&t?.preventDefault)&&(t=null),!(!s.value.trim()&&!t)){if(t){v("search",t),l(!1),s.value="";return}v("search",s.value.trim()),l(!1),s.value=""}},a=()=>{l(!1)},l=t=>{u.value=t},d=()=>{e.nextTick(()=>{c.value?.select()})},p=t=>{(t.ctrlKey||t.metaKey)&&t.key==="k"&&(t.preventDefault(),i()),t.key==="Escape"&&u.value&&a()};e.onMounted(()=>{document.addEventListener("keydown",p)}),e.onBeforeUnmount(()=>{document.removeEventListener("keydown",p)});const g=e.computed(()=>{if(s.value.length<1)return[];const t=r.searchKeys||["title"],k=s.value.toLowerCase();return r.results?.filter(S=>t.some(B=>{const b=S[B];return b&&String(b).toLowerCase().includes(k)}))||[]}),o=t=>{if(!t||t[0]!=="#")return t;const k=parseInt(t.slice(1,3),16),S=parseInt(t.slice(3,5),16),B=parseInt(t.slice(5,7),16);return`${k}, ${S}, ${B}`},n=e.computed(()=>{const{customStyles:t}=r;return t?{bgBody:{backgroundColor:`rgba(${o(t.bgBody)}, 0.9)`},bgInput:{backgroundColor:t.bgInput,boxShadow:`0 0 15px 0 ${t.bgInput}`},customButton:{backgroundColor:t.bgButton||"#ef233c",color:t.colorButton||"#f4faff"}}:{}});return{inputValue:s,inputSearch:c,openedModal:u,closeModal:a,openModal:i,search:m,filterResults:g,custom:n,iconContent:h}},E={class:"tv-search"},M=["innerHTML"],T={class:"tv-search-modal-content-input"},V=["placeholder"],N={key:0,class:"tv-search-results"},$=["onClick"],x={class:"tv-search-results-title"},I={key:1,class:"tv-search-no-results"},D={__name:"TvSearch",props:{placeholder:{type:String,default:""},titleButton:{type:String,default:""},results:{type:Array,default:()=>[]},customStyles:{type:Object,default:()=>({})},searchKeys:{type:Array,default:()=>["title"]},noResultsText:{type:String,default:"No results found for"}},emits:["search"],setup(r,{emit:v}){const s=r,u=v,{inputValue:c,inputSearch:y,openedModal:h,closeModal:i,openModal:m,search:a,filterResults:l,custom:d,iconContent:p}=_(s,u);return(g,o)=>(e.openBlock(),e.createElementBlock(e.Fragment,null,[e.createElementVNode("div",E,[e.createElementVNode("i",{class:"tv-cursor-pointer tv-search-icon",innerHTML:e.unref(p),onClick:o[0]||(o[0]=(...n)=>e.unref(m)&&e.unref(m)(...n))},null,8,M)]),e.unref(h)?(e.openBlock(),e.createElementBlock("div",{key:0,class:"tv-search-modal",onClick:o[4]||(o[4]=e.withModifiers((...n)=>e.unref(i)&&e.unref(i)(...n),["self"])),style:e.normalizeStyle(e.unref(d).bgBody)},[e.createElementVNode("div",{class:"tv-search-modal-content",style:e.normalizeStyle(e.unref(d).bgInput)},[e.createElementVNode("div",T,[e.withDirectives(e.createElementVNode("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=n=>e.isRef(c)?c.value=n:null),onKeyup:o[2]||(o[2]=e.withKeys(n=>e.unref(a)(),["enter"])),placeholder:r.placeholder,class:e.normalizeClass(["tv-search-input",{"tv-radius-none-bl":e.unref(l).length>=1}]),ref_key:"inputSearch",ref:y},null,42,V),[[e.vModelText,e.unref(c)]]),e.createVNode(e.unref(C.TvButton),{runded:"",icon:"search","icon-position":"left",onClick:o[3]||(o[3]=n=>e.unref(a)()),class:e.normalizeClass({"tv-radius-none-br":e.unref(l).length>=1}),customStyle:e.unref(d).customButton},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.titleButton),1)]),_:1},8,["class","customStyle"])]),e.unref(l).length>=1?(e.openBlock(),e.createElementBlock("div",N,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(l),n=>(e.openBlock(),e.createElementBlock("div",{key:n.id,onClick:t=>e.unref(a)(n),class:"tv-cursor-pointer"},[e.renderSlot(g.$slots,"item",{result:n},()=>[e.createElementVNode("p",x,e.toDisplayString(n.title),1)])],8,$))),128))])):e.unref(c)?(e.openBlock(),e.createElementBlock("div",I,[e.renderSlot(g.$slots,"no-results",{},()=>[e.createElementVNode("p",null,e.toDisplayString(r.noResultsText)+' "'+e.toDisplayString(e.unref(c))+'"',1)])])):e.createCommentVNode("",!0)],4)],4)):e.createCommentVNode("",!0)],64))}},f=D;f.install=r=>{r.component("TvSearch",f)};const K={install:f.install};exports.TvSearch=f;exports.TvSearchPlugin=K;exports.default=f;
|
package/dist/tv-search.es.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ref as
|
|
2
|
-
import { TvButton as
|
|
3
|
-
const
|
|
1
|
+
import { ref as M, computed as B, onMounted as E, onBeforeUnmount as N, nextTick as z, createElementBlock as u, openBlock as d, Fragment as $, createElementVNode as a, createCommentVNode as I, unref as e, normalizeStyle as K, withModifiers as D, withDirectives as H, createVNode as O, normalizeClass as L, withKeys as j, isRef as A, vModelText as U, withCtx as q, createTextVNode as F, toDisplayString as k, renderList as P, renderSlot as V } from "vue";
|
|
2
|
+
import { TvButton as G } from "@todovue/tv-button";
|
|
3
|
+
const J = `<svg class="tv-icon-svg" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 129 129">
|
|
4
4
|
<g>
|
|
5
5
|
<path d="M51.6,96.7c11,0,21-3.9,28.8-10.5l35,35c0.8,0.8,1.8,1.2,2.9,1.2s2.1-0.4,2.9-1.2c1.6-1.6,1.6-4.2,0-5.8l-35-35
|
|
6
6
|
c6.5-7.8,10.5-17.9,10.5-28.8c0-24.9-20.2-45.1-45.1-45.1C26.8,6.5,6.5,26.8,6.5,51.6C6.5,76.5,26.8,96.7,51.6,96.7z
|
|
@@ -8,41 +8,46 @@ const q = `<svg class="tv-icon-svg" version="1.1" xmlns="http://www.w3.org/2000/
|
|
|
8
8
|
fill="currentColor"/>
|
|
9
9
|
</g>
|
|
10
10
|
</svg>
|
|
11
|
-
`,
|
|
12
|
-
const
|
|
13
|
-
|
|
11
|
+
`, Q = (s, g) => {
|
|
12
|
+
const r = M(""), v = M(!1), c = M(), b = /* @__PURE__ */ Object.assign({ "../assets/icons/search.svg": J }), C = B(() => b["../assets/icons/search.svg"] || ""), p = () => {
|
|
13
|
+
l(!0), f();
|
|
14
14
|
}, m = (t = null) => {
|
|
15
|
-
if (t && (t instanceof Event || t?.target && t?.preventDefault) && (t = null), !(!
|
|
15
|
+
if (t && (t instanceof Event || t?.target && t?.preventDefault) && (t = null), !(!r.value.trim() && !t)) {
|
|
16
16
|
if (t) {
|
|
17
|
-
g("search", t),
|
|
17
|
+
g("search", t), l(!1), r.value = "";
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
g("search",
|
|
20
|
+
g("search", r.value.trim()), l(!1), r.value = "";
|
|
21
21
|
}
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
},
|
|
22
|
+
}, i = () => {
|
|
23
|
+
l(!1);
|
|
24
|
+
}, l = (t) => {
|
|
25
|
+
v.value = t;
|
|
26
|
+
}, f = () => {
|
|
27
27
|
z(() => {
|
|
28
28
|
c.value?.select();
|
|
29
29
|
});
|
|
30
30
|
}, h = (t) => {
|
|
31
|
-
(t.ctrlKey || t.metaKey) && t.key === "k" && (t.preventDefault(),
|
|
31
|
+
(t.ctrlKey || t.metaKey) && t.key === "k" && (t.preventDefault(), p()), t.key === "Escape" && v.value && i();
|
|
32
32
|
};
|
|
33
|
-
|
|
33
|
+
E(() => {
|
|
34
34
|
document.addEventListener("keydown", h);
|
|
35
|
-
}),
|
|
35
|
+
}), N(() => {
|
|
36
36
|
document.removeEventListener("keydown", h);
|
|
37
37
|
});
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const y = B(() => {
|
|
39
|
+
if (r.value.length < 1) return [];
|
|
40
|
+
const t = s.searchKeys || ["title"], S = r.value.toLowerCase();
|
|
41
|
+
return s.results?.filter((w) => t.some((_) => {
|
|
42
|
+
const x = w[_];
|
|
43
|
+
return x && String(x).toLowerCase().includes(S);
|
|
44
|
+
})) || [];
|
|
45
|
+
}), o = (t) => {
|
|
41
46
|
if (!t || t[0] !== "#") return t;
|
|
42
|
-
const
|
|
43
|
-
return `${
|
|
44
|
-
}, n =
|
|
45
|
-
const { customStyles: t } =
|
|
47
|
+
const S = parseInt(t.slice(1, 3), 16), w = parseInt(t.slice(3, 5), 16), _ = parseInt(t.slice(5, 7), 16);
|
|
48
|
+
return `${S}, ${w}, ${_}`;
|
|
49
|
+
}, n = B(() => {
|
|
50
|
+
const { customStyles: t } = s;
|
|
46
51
|
return t ? {
|
|
47
52
|
bgBody: { backgroundColor: `rgba(${o(t.bgBody)}, 0.9)` },
|
|
48
53
|
bgInput: {
|
|
@@ -56,20 +61,23 @@ const q = `<svg class="tv-icon-svg" version="1.1" xmlns="http://www.w3.org/2000/
|
|
|
56
61
|
} : {};
|
|
57
62
|
});
|
|
58
63
|
return {
|
|
59
|
-
inputValue:
|
|
64
|
+
inputValue: r,
|
|
60
65
|
inputSearch: c,
|
|
61
|
-
openedModal:
|
|
62
|
-
closeModal:
|
|
63
|
-
openModal:
|
|
66
|
+
openedModal: v,
|
|
67
|
+
closeModal: i,
|
|
68
|
+
openModal: p,
|
|
64
69
|
search: m,
|
|
65
|
-
filterResults:
|
|
70
|
+
filterResults: y,
|
|
66
71
|
custom: n,
|
|
67
|
-
iconContent:
|
|
72
|
+
iconContent: C
|
|
68
73
|
};
|
|
69
|
-
},
|
|
74
|
+
}, W = { class: "tv-search" }, X = ["innerHTML"], Y = { class: "tv-search-modal-content-input" }, Z = ["placeholder"], R = {
|
|
70
75
|
key: 0,
|
|
71
76
|
class: "tv-search-results"
|
|
72
|
-
},
|
|
77
|
+
}, tt = ["onClick"], et = { class: "tv-search-results-title" }, nt = {
|
|
78
|
+
key: 1,
|
|
79
|
+
class: "tv-search-no-results"
|
|
80
|
+
}, ot = {
|
|
73
81
|
__name: "TvSearch",
|
|
74
82
|
props: {
|
|
75
83
|
placeholder: {
|
|
@@ -87,85 +95,101 @@ const q = `<svg class="tv-icon-svg" version="1.1" xmlns="http://www.w3.org/2000/
|
|
|
87
95
|
customStyles: {
|
|
88
96
|
type: Object,
|
|
89
97
|
default: () => ({})
|
|
98
|
+
},
|
|
99
|
+
searchKeys: {
|
|
100
|
+
type: Array,
|
|
101
|
+
default: () => ["title"]
|
|
102
|
+
},
|
|
103
|
+
noResultsText: {
|
|
104
|
+
type: String,
|
|
105
|
+
default: "No results found for"
|
|
90
106
|
}
|
|
91
107
|
},
|
|
92
108
|
emits: ["search"],
|
|
93
|
-
setup(
|
|
94
|
-
const
|
|
109
|
+
setup(s, { emit: g }) {
|
|
110
|
+
const r = s, v = g, {
|
|
95
111
|
inputValue: c,
|
|
96
|
-
inputSearch:
|
|
97
|
-
openedModal:
|
|
98
|
-
closeModal:
|
|
112
|
+
inputSearch: b,
|
|
113
|
+
openedModal: C,
|
|
114
|
+
closeModal: p,
|
|
99
115
|
openModal: m,
|
|
100
|
-
search:
|
|
101
|
-
filterResults:
|
|
102
|
-
custom:
|
|
116
|
+
search: i,
|
|
117
|
+
filterResults: l,
|
|
118
|
+
custom: f,
|
|
103
119
|
iconContent: h
|
|
104
|
-
} =
|
|
105
|
-
return (
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
} = Q(r, v);
|
|
121
|
+
return (y, o) => (d(), u($, null, [
|
|
122
|
+
a("div", W, [
|
|
123
|
+
a("i", {
|
|
108
124
|
class: "tv-cursor-pointer tv-search-icon",
|
|
109
125
|
innerHTML: e(h),
|
|
110
126
|
onClick: o[0] || (o[0] = (...n) => e(m) && e(m)(...n))
|
|
111
|
-
}, null, 8,
|
|
127
|
+
}, null, 8, X)
|
|
112
128
|
]),
|
|
113
|
-
e(
|
|
129
|
+
e(C) ? (d(), u("div", {
|
|
114
130
|
key: 0,
|
|
115
131
|
class: "tv-search-modal",
|
|
116
|
-
onClick: o[4] || (o[4] = D((...n) => e(
|
|
117
|
-
style:
|
|
132
|
+
onClick: o[4] || (o[4] = D((...n) => e(p) && e(p)(...n), ["self"])),
|
|
133
|
+
style: K(e(f).bgBody)
|
|
118
134
|
}, [
|
|
119
|
-
|
|
135
|
+
a("div", {
|
|
120
136
|
class: "tv-search-modal-content",
|
|
121
|
-
style:
|
|
137
|
+
style: K(e(f).bgInput)
|
|
122
138
|
}, [
|
|
123
|
-
|
|
124
|
-
|
|
139
|
+
a("div", Y, [
|
|
140
|
+
H(a("input", {
|
|
125
141
|
type: "text",
|
|
126
|
-
"onUpdate:modelValue": o[1] || (o[1] = (n) =>
|
|
127
|
-
onKeyup: o[2] || (o[2] =
|
|
128
|
-
placeholder:
|
|
129
|
-
class:
|
|
142
|
+
"onUpdate:modelValue": o[1] || (o[1] = (n) => A(c) ? c.value = n : null),
|
|
143
|
+
onKeyup: o[2] || (o[2] = j((n) => e(i)(), ["enter"])),
|
|
144
|
+
placeholder: s.placeholder,
|
|
145
|
+
class: L(["tv-search-input", { "tv-radius-none-bl": e(l).length >= 1 }]),
|
|
130
146
|
ref_key: "inputSearch",
|
|
131
|
-
ref:
|
|
132
|
-
}, null, 42,
|
|
133
|
-
[
|
|
147
|
+
ref: b
|
|
148
|
+
}, null, 42, Z), [
|
|
149
|
+
[U, e(c)]
|
|
134
150
|
]),
|
|
135
|
-
|
|
151
|
+
O(e(G), {
|
|
136
152
|
runded: "",
|
|
137
153
|
icon: "search",
|
|
138
154
|
"icon-position": "left",
|
|
139
|
-
onClick: o[3] || (o[3] = (n) => e(
|
|
140
|
-
class:
|
|
141
|
-
customStyle: e(
|
|
155
|
+
onClick: o[3] || (o[3] = (n) => e(i)()),
|
|
156
|
+
class: L({ "tv-radius-none-br": e(l).length >= 1 }),
|
|
157
|
+
customStyle: e(f).customButton
|
|
142
158
|
}, {
|
|
143
|
-
default:
|
|
144
|
-
|
|
159
|
+
default: q(() => [
|
|
160
|
+
F(k(s.titleButton), 1)
|
|
145
161
|
]),
|
|
146
162
|
_: 1
|
|
147
163
|
}, 8, ["class", "customStyle"])
|
|
148
164
|
]),
|
|
149
|
-
e(
|
|
150
|
-
(
|
|
165
|
+
e(l).length >= 1 ? (d(), u("div", R, [
|
|
166
|
+
(d(!0), u($, null, P(e(l), (n) => (d(), u("div", {
|
|
151
167
|
key: n.id,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
},
|
|
155
|
-
|
|
168
|
+
onClick: (t) => e(i)(n),
|
|
169
|
+
class: "tv-cursor-pointer"
|
|
170
|
+
}, [
|
|
171
|
+
V(y.$slots, "item", { result: n }, () => [
|
|
172
|
+
a("p", et, k(n.title), 1)
|
|
173
|
+
])
|
|
174
|
+
], 8, tt))), 128))
|
|
175
|
+
])) : e(c) ? (d(), u("div", nt, [
|
|
176
|
+
V(y.$slots, "no-results", {}, () => [
|
|
177
|
+
a("p", null, k(s.noResultsText) + ' "' + k(e(c)) + '"', 1)
|
|
178
|
+
])
|
|
179
|
+
])) : I("", !0)
|
|
156
180
|
], 4)
|
|
157
|
-
], 4)) :
|
|
181
|
+
], 4)) : I("", !0)
|
|
158
182
|
], 64));
|
|
159
183
|
}
|
|
160
|
-
},
|
|
161
|
-
|
|
162
|
-
|
|
184
|
+
}, T = ot;
|
|
185
|
+
T.install = (s) => {
|
|
186
|
+
s.component("TvSearch", T);
|
|
163
187
|
};
|
|
164
|
-
const
|
|
165
|
-
install:
|
|
188
|
+
const lt = {
|
|
189
|
+
install: T.install
|
|
166
190
|
};
|
|
167
191
|
export {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
192
|
+
T as TvSearch,
|
|
193
|
+
lt as TvSearchPlugin,
|
|
194
|
+
T as default
|
|
171
195
|
};
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"author": "Cristhian Daza",
|
|
5
5
|
"description": "TvSearch provides a fast, accessible, and fully customizable search interface for Vue 3 apps.",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.2.0",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"homepage": "https://ui.todovue.blog/search",
|
|
10
10
|
"repository": {
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
"vue": "^3.5.26"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@todovue/tv-button": "^1.2.
|
|
63
|
+
"@todovue/tv-button": "^1.2.4"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@todovue/tv-demo": "^1.
|
|
66
|
+
"@todovue/tv-demo": "^1.4.4",
|
|
67
67
|
"@vitejs/plugin-vue": "^6.0.3",
|
|
68
|
-
"sass": "^1.97.
|
|
69
|
-
"vite": "^7.3.
|
|
68
|
+
"sass": "^1.97.2",
|
|
69
|
+
"vite": "^7.3.1",
|
|
70
70
|
"vite-plugin-dts": "^4.5.4"
|
|
71
71
|
}
|
|
72
72
|
}
|