|
1 | 1 | <script> |
2 | | - import characters from "./characters.json"; |
| 2 | + import spc from "special-chars"; |
3 | 3 | import { onMount } from "svelte"; |
4 | | - import { Tabs, Tab, TabList, TabPanel } from "svelte-tabs"; |
| 4 | + import { Tabs, Tab, TabContent } from "svelte-materialify/dist"; |
| 5 | + import { isFormElement } from "./utils"; |
5 | 6 |
|
6 | | - let self; |
7 | | - let keyword = ""; |
8 | | - let isSearching = false; |
9 | | - let filteredChars = []; |
| 7 | + /** @type {Document} */ |
| 8 | + export let document; |
| 9 | + export let id; |
| 10 | + export let offset; |
10 | 11 |
|
11 | 12 | const { activeElement } = document; |
12 | | - const { top, left, height } = activeElement.getBoundingClientRect(); |
13 | | - const categories = Object.keys(characters); |
14 | | - const allChars = categories.map((category) => characters[category]).flat(); |
| 13 | + const allChars = Object.keys(spc) |
| 14 | + .map((category) => spc[category]) |
| 15 | + .flat(); |
| 16 | +
|
| 17 | + let self; |
| 18 | + let characters = { ...spc, "🔎": [] }; |
| 19 | + let categories = Object.keys(characters); |
| 20 | + let searchText = ""; |
15 | 21 |
|
16 | 22 | function insert({ target }) { |
17 | | - const { tagName } = activeElement; |
| 23 | + const attribute = isFormElement(activeElement) ? "value" : "textContent"; |
18 | 24 | activeElement.focus(); |
19 | | - tagName === "INPUT" || tagName === "TEXTAREA" |
20 | | - ? (activeElement.value += target.textContent) |
21 | | - : (activeElement.textContent += target.textContent); |
| 25 | + activeElement[attribute] += target.textContent; |
22 | 26 | } |
23 | 27 |
|
24 | 28 | function dismiss() { |
25 | 29 | self.remove(); |
26 | 30 | } |
27 | 31 |
|
28 | | - function search({ key }) { |
29 | | - isSearching = true; |
30 | | - keyword += key; |
31 | | - // filteredChars = allChars.filter(({ name }) => name.includes(keyword)); |
| 32 | + function stopPropagation(event) { |
| 33 | + event.stopPropagation(); |
32 | 34 | } |
33 | 35 |
|
34 | 36 | onMount(() => { |
35 | | - self.addEventListener("click", (event) => event.stopPropagation()); |
36 | 37 | document.addEventListener("click", dismiss); |
37 | | - activeElement.addEventListener("keypress", search); |
| 38 | + self.addEventListener("click", stopPropagation); |
| 39 | + activeElement.addEventListener("click", stopPropagation); |
38 | 40 | }); |
| 41 | +
|
| 42 | + $: if (searchText) { |
| 43 | + let count = 0; |
| 44 | + const condition = ({ name }) => |
| 45 | + name.includes(searchText.toUpperCase()) && ++count <= 150; |
| 46 | + characters["🔎"] = allChars.filter(condition); |
| 47 | + } else { |
| 48 | + characters["🔎"] = []; |
| 49 | + } |
39 | 50 | </script> |
40 | 51 |
|
41 | | -<div |
42 | | - bind:this={self} |
43 | | - id="spc" |
44 | | - style="top: {top + height}px; left: {left + 3}px" |
45 | | -> |
46 | | - <p> |
47 | | - Keep typing to find a character |
48 | | - <button aria-label="Close" on:click={dismiss}>x</button> |
49 | | - </p> |
50 | | - <Tabs> |
| 52 | +<main bind:this={self} {id} style="top: {offset.y}px; left: {offset.x}px"> |
| 53 | + <header> |
| 54 | + <input type="search" placeholder="Search..." bind:value={searchText} /> |
| 55 | + <button aria-label="Close" on:click={dismiss}>✕</button> |
| 56 | + </header> |
| 57 | + <Tabs vertical value={searchText ? categories.length - 1 : 0}> |
51 | 58 | {#each categories as category} |
52 | | - <TabPanel> |
53 | | - {#if isSearching} |
54 | | - <ul> |
55 | | - {#each filteredChars as { char }} |
56 | | - <li on:click={insert}>{char}</li> |
57 | | - {/each} |
58 | | - </ul> |
59 | | - {:else} |
60 | | - <ul> |
61 | | - {#each characters[category] as { char }} |
62 | | - <li on:click={insert}>{char}</li> |
63 | | - {/each} |
64 | | - </ul> |
65 | | - {/if} |
66 | | - </TabPanel> |
| 59 | + <TabContent> |
| 60 | + <ul> |
| 61 | + {#each characters[category] as { name, char }} |
| 62 | + <li title={name} on:click={insert}>{char}</li> |
| 63 | + {/each} |
| 64 | + </ul> |
| 65 | + </TabContent> |
67 | 66 | {/each} |
68 | | - |
69 | | - <TabList> |
| 67 | + <div slot="tabs"> |
70 | 68 | {#each categories as category} |
71 | 69 | <Tab>{category}</Tab> |
72 | 70 | {/each} |
73 | | - </TabList> |
| 71 | + </div> |
74 | 72 | </Tabs> |
75 | | -</div> |
| 73 | +</main> |
76 | 74 |
|
77 | 75 | <style> |
78 | 76 | :root { |
|
82 | 80 | #spc { |
83 | 81 | z-index: 9999; |
84 | 82 | position: absolute; |
85 | | - background: #f9f9f9; |
86 | | - box-shadow: 0 0 4px rgba(0, 0, 0, 0.3); |
| 83 | + background: #f8f8f8; |
| 84 | + box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); |
87 | 85 | font-size: 16px; |
| 86 | + padding-bottom: var(--spacing); |
88 | 87 | } |
89 | 88 |
|
90 | | - p { |
91 | | - margin: var(--spacing); |
92 | | - color: #666; |
| 89 | + header { |
| 90 | + display: flex; |
| 91 | + padding: var(--spacing); |
| 92 | + } |
| 93 | +
|
| 94 | + input[type="search"] { |
| 95 | + outline-offset: initial; |
| 96 | + padding: 0.1em 0.3em; |
| 97 | + margin-right: var(--spacing); |
| 98 | + background: #fff; |
| 99 | + box-sizing: border-box; |
| 100 | + flex-grow: 2; |
93 | 101 | } |
94 | 102 |
|
95 | 103 | button[aria-label="Close"] { |
96 | | - float: right; |
97 | | - font-family: monospace; |
98 | 104 | background: #eee; |
99 | 105 | color: #000; |
100 | 106 | border: none; |
101 | 107 | padding: 0; |
102 | | - margin: calc(var(--spacing) * -1 + 0.1em); |
103 | 108 | width: 30px; |
104 | 109 | height: 30px; |
105 | 110 | line-height: 0; |
|
111 | 116 | } |
112 | 117 |
|
113 | 118 | ul { |
114 | | - width: 400px; |
| 119 | + width: 310px; |
115 | 120 | height: 300px; |
| 121 | + margin: 0; |
116 | 122 | padding: var(--spacing) !important; |
117 | 123 | text-align: center; |
| 124 | + box-sizing: border-box; |
118 | 125 | overflow: auto; |
119 | 126 | list-style: none; |
120 | | - display: grid; |
121 | | - grid-template-columns: repeat(12, auto); |
| 127 | + display: flex; |
| 128 | + flex-wrap: wrap; |
| 129 | + } |
| 130 | +
|
| 131 | + li { |
| 132 | + width: 25px; |
| 133 | + height: 25px; |
| 134 | + display: flex; |
| 135 | + justify-content: center; |
| 136 | + align-items: center; |
| 137 | + color: #000; |
122 | 138 | } |
123 | 139 |
|
124 | 140 | li:hover { |
125 | 141 | background: #eee; |
126 | 142 | cursor: default; |
127 | 143 | } |
| 144 | +
|
| 145 | + :global(.s-window) { |
| 146 | + background: #fff; |
| 147 | + } |
128 | 148 | </style> |
0 commit comments