@todovue/tv-search 1.1.1 → 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/LICENSE +1 -1
- package/README.md +42 -51
- package/dist/tv-search.cjs.js +2 -2
- package/dist/tv-search.css +1 -1
- package/dist/tv-search.es.js +104 -80
- package/package.json +10 -9
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
A fast, accessible, and fully customizable search interface component for Vue 3 applications. Provides an elegant modal search experience with keyboard shortcuts, real-time filtering, and complete style customization. Works seamlessly in Single Page Apps or Server-Side Rendered (SSR) environments (e.g. Nuxt 3).
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@todovue/tv-search)
|
|
8
|
-
[](https://app.netlify.com/projects/tv-search-demo/deploys)
|
|
9
8
|
[](https://www.npmjs.com/package/@todovue/tv-search)
|
|
10
9
|
[](https://www.npmjs.com/package/@todovue/tv-search)
|
|
11
10
|

|
|
@@ -15,14 +14,13 @@ A fast, accessible, and fully customizable search interface component for Vue 3
|
|
|
15
14
|

|
|
16
15
|

|
|
17
16
|
|
|
18
|
-
> Demo: https://
|
|
17
|
+
> Demo: https://ui.todovue.blog/search
|
|
19
18
|
|
|
20
|
-
---
|
|
21
19
|
## Table of Contents
|
|
22
20
|
- [Features](#features)
|
|
23
21
|
- [Installation](#installation)
|
|
24
22
|
- [Quick Start (SPA)](#quick-start-spa)
|
|
25
|
-
- [Nuxt
|
|
23
|
+
- [Nuxt 4 / SSR Usage](#nuxt-4--ssr-usage)
|
|
26
24
|
- [Component Registration Options](#component-registration-options)
|
|
27
25
|
- [Props](#props)
|
|
28
26
|
- [Events](#events)
|
|
@@ -31,13 +29,11 @@ A fast, accessible, and fully customizable search interface component for Vue 3
|
|
|
31
29
|
- [Results Data Structure](#results-data-structure)
|
|
32
30
|
- [Accessibility](#accessibility)
|
|
33
31
|
- [SSR Notes](#ssr-notes)
|
|
34
|
-
- [Roadmap](#roadmap)
|
|
35
32
|
- [Development](#development)
|
|
36
33
|
- [Contributing](#contributing)
|
|
37
34
|
- [Changelog](#changelog)
|
|
38
35
|
- [License](#license)
|
|
39
36
|
|
|
40
|
-
---
|
|
41
37
|
## Features
|
|
42
38
|
- **Keyboard-first UX**: Open with `Ctrl+K` / `Cmd+K`, close with `Esc`
|
|
43
39
|
- **Real-time filtering**: Search as you type with instant results
|
|
@@ -46,11 +42,10 @@ A fast, accessible, and fully customizable search interface component for Vue 3
|
|
|
46
42
|
- **Accessible**: Built with semantic HTML and keyboard navigation
|
|
47
43
|
- **Lightweight**: Minimal dependencies, Vue 3 marked as peer dependency
|
|
48
44
|
- **SSR compatible**: Works in Nuxt 3 and other SSR frameworks
|
|
49
|
-
- **
|
|
45
|
+
- **Autofocus**: Input field receives focus automatically when opened
|
|
50
46
|
- **Click-away close**: Modal closes when clicking outside the content area
|
|
51
47
|
- **Flexible results**: Pass any array of searchable items with custom properties
|
|
52
48
|
|
|
53
|
-
---
|
|
54
49
|
## Installation
|
|
55
50
|
Using npm:
|
|
56
51
|
```bash
|
|
@@ -65,7 +60,6 @@ Using pnpm:
|
|
|
65
60
|
pnpm add @todovue/tv-search
|
|
66
61
|
```
|
|
67
62
|
|
|
68
|
-
---
|
|
69
63
|
## Quick Start (SPA)
|
|
70
64
|
Global registration (main.js / main.ts):
|
|
71
65
|
```js
|
|
@@ -125,8 +119,7 @@ function handleSearch(query) {
|
|
|
125
119
|
</template>
|
|
126
120
|
```
|
|
127
121
|
|
|
128
|
-
|
|
129
|
-
## Nuxt 3 / SSR Usage
|
|
122
|
+
## Nuxt 4 / SSR Usage
|
|
130
123
|
Create a plugin file: `plugins/tv-search.client.ts` (client-only is recommended since it uses keyboard events):
|
|
131
124
|
```ts
|
|
132
125
|
// nuxt.config.ts
|
|
@@ -166,7 +159,6 @@ import { TvSearch } from '@todovue/tv-search'
|
|
|
166
159
|
</script>
|
|
167
160
|
```
|
|
168
161
|
|
|
169
|
-
---
|
|
170
162
|
## Component Registration Options
|
|
171
163
|
| Approach | When to use |
|
|
172
164
|
|------------------------------------------------------|----------------------------------------------------|
|
|
@@ -175,14 +167,15 @@ import { TvSearch } from '@todovue/tv-search'
|
|
|
175
167
|
| Local named import `import TvSearch from '...'` | Single page usage / code splitting |
|
|
176
168
|
| Nuxt plugin `.client.ts` | SSR apps with client-side interactions |
|
|
177
169
|
|
|
178
|
-
---
|
|
179
170
|
## Props
|
|
180
|
-
| Prop
|
|
181
|
-
|
|
182
|
-
| placeholder
|
|
183
|
-
| titleButton
|
|
184
|
-
| results
|
|
185
|
-
| 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` |
|
|
186
179
|
|
|
187
180
|
### customStyles Object
|
|
188
181
|
Customize the appearance by passing a `customStyles` object with any of these properties:
|
|
@@ -194,11 +187,10 @@ Customize the appearance by passing a `customStyles` object with any of these pr
|
|
|
194
187
|
| bgButton | String | `"#EF233C"` | Background color of the search button |
|
|
195
188
|
| colorButton | String | `"#F4FAFF"` | Text color of the search button |
|
|
196
189
|
|
|
197
|
-
---
|
|
198
190
|
## Events
|
|
199
|
-
| Event | Payload Type
|
|
200
|
-
|
|
201
|
-
| 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. |
|
|
202
194
|
|
|
203
195
|
Example:
|
|
204
196
|
```vue
|
|
@@ -217,7 +209,33 @@ function handleSearch(query) {
|
|
|
217
209
|
</script>
|
|
218
210
|
```
|
|
219
211
|
|
|
220
|
-
|
|
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
|
+
|
|
221
239
|
## Keyboard Shortcuts
|
|
222
240
|
| Shortcut | Action |
|
|
223
241
|
|------------------------|-----------------------------------|
|
|
@@ -226,7 +244,6 @@ function handleSearch(query) {
|
|
|
226
244
|
| `Enter` | Execute search with current input |
|
|
227
245
|
| Click outside modal | Close the search modal |
|
|
228
246
|
|
|
229
|
-
---
|
|
230
247
|
## Customization (Styles / Theming)
|
|
231
248
|
You can override the default color scheme by passing a `customStyles` object:
|
|
232
249
|
|
|
@@ -289,7 +306,6 @@ const brandTheme = {
|
|
|
289
306
|
}
|
|
290
307
|
```
|
|
291
308
|
|
|
292
|
-
---
|
|
293
309
|
## Results Data Structure
|
|
294
310
|
The `results` prop expects an array of objects with the following structure:
|
|
295
311
|
|
|
@@ -325,7 +341,6 @@ const results = [
|
|
|
325
341
|
|
|
326
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.
|
|
327
343
|
|
|
328
|
-
---
|
|
329
344
|
## Accessibility
|
|
330
345
|
- **Keyboard navigation**: Full support for `Ctrl+K`/`Cmd+K` to open, `Esc` to close, and `Enter` to search
|
|
331
346
|
- **Focus management**: Input automatically receives focus when modal opens and is selected for immediate typing
|
|
@@ -338,7 +353,6 @@ const results = [
|
|
|
338
353
|
- Ensure sufficient color contrast when using `customStyles`
|
|
339
354
|
- Consider adding `aria-label` attributes for screen reader support in future versions
|
|
340
355
|
|
|
341
|
-
---
|
|
342
356
|
## SSR Notes
|
|
343
357
|
- **Safe for SSR**: No direct DOM access (`window` / `document`) during module initialization
|
|
344
358
|
- **Event listeners**: Keyboard event listeners are registered in `onMounted` and cleaned up in `onBeforeUnmount`
|
|
@@ -348,25 +362,6 @@ const results = [
|
|
|
348
362
|
- For Vue/Vite SPA: `import '@todovue/tv-search/style.css'` in `main.ts`
|
|
349
363
|
- For Nuxt 3/4: Add `'@todovue/tv-search/style.css'` to the `css` array in `nuxt.config.ts`
|
|
350
364
|
|
|
351
|
-
---
|
|
352
|
-
## Roadmap
|
|
353
|
-
| Feature | Status |
|
|
354
|
-
|------------------------------------------|-------------|
|
|
355
|
-
| Support for result `url` navigation | Planned |
|
|
356
|
-
| Display `description` in results | Planned |
|
|
357
|
-
| Customizable search icon | Planned |
|
|
358
|
-
| Multiple keyboard shortcut options | Considering |
|
|
359
|
-
| Result categorization / grouping | Considering |
|
|
360
|
-
| Highlight matching text in results | Considering |
|
|
361
|
-
| Recent searches history | Considering |
|
|
362
|
-
| Loading state indicator | Considering |
|
|
363
|
-
| Pagination for large result sets | Considering |
|
|
364
|
-
| Fuzzy search / advanced filtering | Considering |
|
|
365
|
-
| Theming via CSS variables | Considering |
|
|
366
|
-
| TypeScript type definitions improvement | Planned |
|
|
367
|
-
| ARIA attributes enhancement | Planned |
|
|
368
|
-
|
|
369
|
-
---
|
|
370
365
|
## Development
|
|
371
366
|
Clone the repository and install dependencies:
|
|
372
367
|
```bash
|
|
@@ -392,7 +387,6 @@ yarn build:demo
|
|
|
392
387
|
|
|
393
388
|
The demo is served from Vite using `index.html` + `src/demo` examples.
|
|
394
389
|
|
|
395
|
-
---
|
|
396
390
|
## Contributing
|
|
397
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.
|
|
398
392
|
|
|
@@ -403,14 +397,11 @@ Contributions are welcome! Please read our [Contributing Guidelines](https://git
|
|
|
403
397
|
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
404
398
|
5. Open a Pull Request
|
|
405
399
|
|
|
406
|
-
---
|
|
407
400
|
## Changelog
|
|
408
401
|
See [CHANGELOG.md](https://github.com/TODOvue/tv-search/blob/main/CHANGELOG.md) for release history and version changes.
|
|
409
402
|
|
|
410
|
-
---
|
|
411
403
|
## License
|
|
412
404
|
[MIT](https://github.com/TODOvue/tv-search/blob/main/LICENSE) © TODOvue
|
|
413
405
|
|
|
414
|
-
---
|
|
415
406
|
### Attributions
|
|
416
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.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@charset "UTF-8";.tv-cursor-pointer{cursor:pointer}.tv-radius-none-bl{border-bottom-left-radius:0!important}.tv-radius-none-br{border-bottom-right-radius:0!important}.tv-search-icon{border:2px solid #B9C4DF}.tv-search-modal{background-color:#f8fafcb3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.tv-search-modal .tv-search-modal-content{background-color:#b9c4df;box-shadow:0 0 15px #b9c4df}.tv-search-icon{display:inline-block;height:32px;padding:2px;width:32px}.tv-search-modal{align-items:flex-start;display:flex;height:100%;justify-content:center;left:0;padding-top:100px;position:fixed;top:0;width:100%;z-index:1000}.tv-search-modal .tv-search-modal-content{border-radius:5px;padding:20px;width:550px}.tv-search-modal .tv-search-modal-content .tv-search-input{border-radius:10px 0 0 10px;border:none;font-size:16px;height:44px;outline:none;padding:10px;width:100%}.tv-search-modal .tv-search-modal-content .tv-btn{border-radius:0 10px 10px 0}.tv-search-modal .tv-search-modal-content .tv-search-modal-content-input{display:flex}.tv-search-modal .tv-search-modal-content .tv-search-results{border-radius:0 0 10px 10px;background:#f8fafc;color:#1e293b;display:inline-block;max-height:200px;
|
|
1
|
+
@charset "UTF-8";.tv-cursor-pointer{cursor:pointer}.tv-radius-none-bl{border-bottom-left-radius:0!important}.tv-radius-none-br{border-bottom-right-radius:0!important}.tv-search-icon{border:2px solid #B9C4DF}.tv-search-modal{background-color:#f8fafcb3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.tv-search-modal .tv-search-modal-content{background-color:#b9c4df;box-shadow:0 0 15px #b9c4df}.tv-search-icon{display:inline-block;height:32px;padding:2px;width:32px}.tv-search-modal{align-items:flex-start;display:flex;height:100%;justify-content:center;left:0;padding-top:100px;position:fixed;top:0;width:100%;z-index:1000}.tv-search-modal .tv-search-modal-content{border-radius:5px;padding:20px;width:550px;max-width:90%}.tv-search-modal .tv-search-modal-content .tv-search-input{border-radius:10px 0 0 10px;border:none;font-size:16px;height:44px;outline:none;padding:10px;width:100%}.tv-search-modal .tv-search-modal-content .tv-btn{border-radius:0 10px 10px 0}.tv-search-modal .tv-search-modal-content .tv-search-modal-content-input{display:flex}.tv-search-modal .tv-search-modal-content .tv-search-results{border-radius:0 0 10px 10px;background:#f8fafc;color:#1e293b;display:inline-block;max-height:200px;overflow-y:auto;transition:all .3s ease;width:100%}.tv-search-modal .tv-search-modal-content .tv-search-results-title{font-size:1rem;font-weight:600;padding:.7rem 1rem}.tv-search-modal .tv-search-modal-content .tv-search-results-title:hover{background:#0e131f33}.tv-search-modal .tv-search-modal-content .tv-search-results-title:active{background:#0e131f66}.dark-mode .tv-search-icon{border:2px solid #0E131F}.dark-mode .tv-search-modal{background-color:#161e31b3;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.dark-mode .tv-search-modal .tv-search-modal-content{background-color:#0e131f;box-shadow:0 0 15px #0e131f}.dark-mode .tv-search-modal .tv-search-modal-content .tv-search-results{background:#161e31;color:#cbd5e1}@media(max-width:768px){.tv-search-modal{padding-top:50px}.tv-search-modal .tv-search-modal-content{padding:15px;width:95%;max-width:95%}.tv-search-modal .tv-search-modal-content .tv-search-results{max-height:150px}}@media(max-width:480px){.tv-search-modal{padding-top:30px;align-items:flex-start}.tv-search-modal .tv-search-modal-content{padding:10px}.tv-search-modal .tv-search-modal-content .tv-search-input{font-size:14px;height:40px;padding:8px}.tv-search-modal .tv-search-modal-content .tv-search-results{max-height:120px}.tv-search-modal .tv-search-modal-content .tv-search-results-title{font-size:.9rem;padding:.5rem .8rem}}
|
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,11 +4,12 @@
|
|
|
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
|
+
"homepage": "https://ui.todovue.blog/search",
|
|
9
10
|
"repository": {
|
|
10
11
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/TODOvue/tv-search.git"
|
|
12
|
+
"url": "git+https://github.com/TODOvue/tv-search.git"
|
|
12
13
|
},
|
|
13
14
|
"bugs": {
|
|
14
15
|
"url": "https://github.com/TODOvue/tv-search/issues"
|
|
@@ -56,16 +57,16 @@
|
|
|
56
57
|
"build:demo": "cp README.md public/ && cp CHANGELOG.md public/ && VITE_BUILD_TARGET=demo vite build"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
59
|
-
"vue": "^3.
|
|
60
|
+
"vue": "^3.5.26"
|
|
60
61
|
},
|
|
61
62
|
"dependencies": {
|
|
62
|
-
"@todovue/tv-button": "^1.2.
|
|
63
|
+
"@todovue/tv-button": "^1.2.4"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
|
-
"@todovue/tv-demo": "^1.
|
|
66
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
67
|
-
"sass": "^1.
|
|
68
|
-
"vite": "^7.
|
|
69
|
-
"vite-plugin-dts": "^4.
|
|
66
|
+
"@todovue/tv-demo": "^1.4.4",
|
|
67
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
68
|
+
"sass": "^1.97.2",
|
|
69
|
+
"vite": "^7.3.1",
|
|
70
|
+
"vite-plugin-dts": "^4.5.4"
|
|
70
71
|
}
|
|
71
72
|
}
|