rails_icons 1.7.1 → 1.9.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/README.md +94 -1
- data/app/assets/javascripts/rails_icons/minisearch.min.js +8 -0
- data/app/assets/javascripts/rails_icons/preview/preview.js +392 -0
- data/app/assets/stylesheets/rails_icons/preview.css +169 -0
- data/app/controllers/rails_icons/previews_controller.rb +28 -0
- data/app/controllers/rails_icons/sprites_controller.rb +11 -0
- data/app/models/rails_icons/preview/tags/animated.yml +4 -0
- data/app/models/rails_icons/preview/tags/feather.yml +287 -0
- data/app/models/rails_icons/preview/tags/flags.yml +271 -0
- data/app/models/rails_icons/preview/tags/heroicons.yml +324 -0
- data/app/models/rails_icons/preview/tags/linear.yml +170 -0
- data/app/models/rails_icons/preview/tags/lucide.yml +1703 -0
- data/app/models/rails_icons/preview/tags/phosphor.yml +1512 -0
- data/app/models/rails_icons/preview/tags/radix.yml +332 -0
- data/app/models/rails_icons/preview/tags/sidekickicons.yml +58 -0
- data/app/models/rails_icons/preview/tags/tabler.yml +5021 -0
- data/app/models/rails_icons/preview/tags/weather.yml +219 -0
- data/app/models/rails_icons/preview/tags.rb +14 -0
- data/app/models/rails_icons/preview.rb +53 -0
- data/app/views/rails_icons/previews/show.html.erb +59 -0
- data/config/routes.rb +8 -0
- data/lib/generators/rails_icons/initializer_generator.rb +2 -2
- data/lib/generators/rails_icons/install_generator.rb +8 -0
- data/lib/rails_icons/engine.rb +23 -0
- data/lib/rails_icons/generate_tags.rb +44 -0
- data/lib/rails_icons/helpers/icon_helper.rb +27 -0
- data/lib/rails_icons/helpers/sprite_helper.rb +52 -0
- data/lib/rails_icons/version.rb +1 -1
- data/lib/rails_icons.rb +7 -0
- data/lib/ruby_lsp/rails_icons/addon.rb +2 -0
- data/rails_icons.gemspec +1 -1
- metadata +26 -8
- data/app/assets/svg/rails_icons/icons/animated/bouncing-dots.svg +0 -1
- data/app/assets/svg/rails_icons/icons/animated/faded-spinner.svg +0 -1
- data/app/assets/svg/rails_icons/icons/animated/fading-dots.svg +0 -1
- data/app/assets/svg/rails_icons/icons/animated/trailing-spinner.svg +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7cd203323e3d677c596eafcbc471b132b29bae354de297cdb96aee0da7a9b842
|
|
4
|
+
data.tar.gz: 777f5a17de05f8c66a7b5853af51b170bef1a55011fd9599cb397ef82aa34b0c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1daf94a537ad62da679b0c09696beeaeb8d74e207d393f126b682d54f134eb39468ae943080079dd16b7caa80a97f6b8b6721dc626e8a4bdbb0bc318c1315c43
|
|
7
|
+
data.tar.gz: cafc3c92e4cc3b8146eab5879ea8549f6fbd5b0929487a18ed00c5a3f0c8a775f8d17289e61431e808a8b29fb9b41b7734e2ca976b4f72a5ed97106cbe05fbab
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rails_icons (1.
|
|
5
|
-
icons (~> 0.
|
|
4
|
+
rails_icons (1.9.0)
|
|
5
|
+
icons (~> 0.9.0)
|
|
6
6
|
rails (>= 7.0)
|
|
7
7
|
|
|
8
8
|
GEM
|
|
@@ -102,7 +102,7 @@ GEM
|
|
|
102
102
|
activesupport (>= 6.1)
|
|
103
103
|
i18n (1.14.7)
|
|
104
104
|
concurrent-ruby (~> 1.0)
|
|
105
|
-
icons (0.
|
|
105
|
+
icons (0.9.0)
|
|
106
106
|
nokogiri (~> 1.16, >= 1.16.4)
|
|
107
107
|
io-console (0.8.1)
|
|
108
108
|
irb (1.15.2)
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ icon "check", class: "text-gray-500"
|
|
|
12
12
|
icon "apple", library: "simple_icons", class: "text-black"
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The icons are sourced directly from their respective GitHub repositories, ensuring Rails Icons remain lightweight.
|
|
15
|
+
The icons are sourced directly from their respective GitHub repositories via the [Icons](https://github.com/Rails-Designer/icons) gem, ensuring Rails Icons remain lightweight.
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
**Sponsored By [Rails Designer](https://railsdesigner.com/)**
|
|
@@ -46,6 +46,8 @@ rails generate rails_icons:install --library=heroicons
|
|
|
46
46
|
rails generate rails_icons:install --libraries=heroicons lucide
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
The generator also mounts an icon preview at `/rails_icons` where you can browse and search all your available icons. This route is open by default, so restrict it in production if needed.
|
|
50
|
+
|
|
49
51
|
|
|
50
52
|
## Usage
|
|
51
53
|
|
|
@@ -75,6 +77,96 @@ icon "check", stroke_width: 2
|
|
|
75
77
|
```
|
|
76
78
|
|
|
77
79
|
|
|
80
|
+
## Sprites
|
|
81
|
+
|
|
82
|
+
Rails Icons supports SVG sprites for improved performance. Instead of inlining each icon's full SVG, sprite icons reference a shared set of `<symbol>` definitions via `<use href="…">`.
|
|
83
|
+
|
|
84
|
+
### Configuration
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
# config/initializers/rails_icons.rb
|
|
88
|
+
RailsIcons.configure do |config|
|
|
89
|
+
config.default_library = "heroicons"
|
|
90
|
+
config.default_variant = "outline"
|
|
91
|
+
|
|
92
|
+
# Where `sprite_icon` references symbols. Defaults to the gem-served
|
|
93
|
+
# endpoint below. Set to nil to use inline mode (`<%= icons_sprite %>` in layout).
|
|
94
|
+
config.default_sprite_location = "/rails_icons/sprite.svg"
|
|
95
|
+
|
|
96
|
+
# Set to true to validate that referenced icons exist on disk
|
|
97
|
+
config.validate_sprite_icons = false
|
|
98
|
+
|
|
99
|
+
# Define which icons to include in the sprite
|
|
100
|
+
config.sprite = {
|
|
101
|
+
heroicons: {
|
|
102
|
+
outline: %w[check chevron-down menu search x],
|
|
103
|
+
mini: %w[check chevron-down]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
### External sprite (default)
|
|
111
|
+
|
|
112
|
+
Rails Icons serves the sprite at `/rails_icons/sprite.svg` out of the box — no controller, route or MIME type setup needed. The endpoint sits at the host app level, so it stays reachable even when the preview engine is mounted behind authentication.
|
|
113
|
+
```erb
|
|
114
|
+
<%= sprite_icon "check" %>
|
|
115
|
+
<%# renders: <svg><use href="/rails_icons/sprite.svg#heroicons_outline_check"></use></svg> %>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Point at a precompiled file or a CDN by changing the location:
|
|
119
|
+
```ruby
|
|
120
|
+
config.default_sprite_location = "https://cdn.example.com/sprite_icons.svg"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Override per icon:
|
|
124
|
+
```erb
|
|
125
|
+
<%= sprite_icon "check", sprite_location: "/assets/sprites.svg" %>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
### Inline sprite
|
|
130
|
+
|
|
131
|
+
Set the location to `nil` and embed the sprite directly in your layout:
|
|
132
|
+
```ruby
|
|
133
|
+
config.default_sprite_location = nil
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```erb
|
|
137
|
+
<body>
|
|
138
|
+
<%= icons_sprite %>
|
|
139
|
+
|
|
140
|
+
<%= sprite_icon "check" %>
|
|
141
|
+
<%= sprite_icon "search", class: "text-blue-500" %>
|
|
142
|
+
<%= sprite_icon "menu", data: { controller: "nav" } %>
|
|
143
|
+
</body>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
You can also generate a sprite for a specific set of icons:
|
|
147
|
+
```erb
|
|
148
|
+
<%= icons_sprite(["check", "search"], library: "heroicons", variant: "outline") %>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
### Helpers
|
|
153
|
+
|
|
154
|
+
`sprite_icon` accepts the same options as `icon`:
|
|
155
|
+
```ruby
|
|
156
|
+
sprite_icon "check"
|
|
157
|
+
sprite_icon "check", library: "heroicons", variant: "mini"
|
|
158
|
+
sprite_icon "check", class: "size-6", data: { controller: "swap" }, stroke_width: 2
|
|
159
|
+
sprite_icon "check", sprite_location: "/sprite.svg"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`icons_sprite` generates the inline `<svg>` containing `<symbol>` definitions:
|
|
163
|
+
```ruby
|
|
164
|
+
icons_sprite # all configured icons
|
|
165
|
+
icons_sprite ["check", "search"] # specific icons
|
|
166
|
+
icons_sprite ["check", "search"], library: "heroicons", variant: "outline" # with library/variant
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
|
|
78
170
|
## First-party libraries
|
|
79
171
|
|
|
80
172
|
- [Boxicons](https://railsdesigner.com/open-source/rails-icons/boxicons/) (1600+ icons)
|
|
@@ -137,6 +229,7 @@ rails generate rails_icons:sync --libraries=heroicons lucide
|
|
|
137
229
|
- [Rails Designer UI Components](https://railsdesigner.com/components/) — The first professionally-designed UI components library for Ruby on Rails apps
|
|
138
230
|
- [Chirp Form](https://chirpform.com/) — Add forms to any site. Display responses anywhere
|
|
139
231
|
- [Helptail](https://helptail.com/) — Put your routine tasks on autopilot
|
|
232
|
+
- [Seal Static](https://sealstatic.com/) — Host sites for every need
|
|
140
233
|
|
|
141
234
|
|
|
142
235
|
## Contributing
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minified by jsDelivr using Terser v5.39.0.
|
|
3
|
+
* Original file: /npm/minisearch@7.2.0/dist/umd/index.js
|
|
4
|
+
*
|
|
5
|
+
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
|
6
|
+
*/
|
|
7
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).MiniSearch=e()}(this,(function(){"use strict";const t="KEYS",e="VALUES",s="";class i{constructor(t,e){const s=t._tree,i=Array.from(s.keys());this.set=t,this._type=e,this._path=i.length>0?[{node:s,keys:i}]:[]}next(){const t=this.dive();return this.backtrack(),t}dive(){if(0===this._path.length)return{done:!0,value:void 0};const{node:t,keys:e}=n(this._path);if(n(e)===s)return{done:!1,value:this.result()};const i=t.get(n(e));return this._path.push({node:i,keys:Array.from(i.keys())}),this.dive()}backtrack(){if(0===this._path.length)return;const t=n(this._path).keys;t.pop(),t.length>0||(this._path.pop(),this.backtrack())}key(){return this.set._prefix+this._path.map((({keys:t})=>n(t))).filter((t=>t!==s)).join("")}value(){return n(this._path).node.get(s)}result(){switch(this._type){case e:return this.value();case t:return this.key();default:return[this.key(),this.value()]}}[Symbol.iterator](){return this}}const n=t=>t[t.length-1],o=(t,e,i,n,r,c,h,u)=>{const d=c*h;t:for(const a of t.keys())if(a===s){const e=r[d-1];e<=i&&n.set(u,[t.get(a),e])}else{let s=c;for(let t=0;t<a.length;++t,++s){const n=a[t],o=h*s,c=o-h;let u=r[o];const d=Math.max(0,s-i-1),l=Math.min(h-1,s+i);for(let t=d;t<l;++t){const s=n!==e[t],i=r[c+t]+ +s,h=r[c+t+1]+1,d=r[o+t]+1,a=r[o+t+1]=Math.min(i,h,d);a<u&&(u=a)}if(u>i)continue t}o(t.get(a),e,i,n,r,s,h,u+a)}};class r{constructor(t=new Map,e=""){this._size=void 0,this._tree=t,this._prefix=e}atPrefix(t){if(!t.startsWith(this._prefix))throw new Error("Mismatched prefix");const[e,i]=c(this._tree,t.slice(this._prefix.length));if(void 0===e){const[e,n]=f(i);for(const i of e.keys())if(i!==s&&i.startsWith(n)){const s=new Map;return s.set(i.slice(n.length),e.get(i)),new r(s,t)}}return new r(e,t)}clear(){this._size=void 0,this._tree.clear()}delete(t){return this._size=void 0,d(this._tree,t)}entries(){return new i(this,"ENTRIES")}forEach(t){for(const[e,s]of this)t(e,s,this)}fuzzyGet(t,e){return((t,e,s)=>{const i=new Map;if(void 0===e)return i;const n=e.length+1,r=n+s,c=new Uint8Array(r*n).fill(s+1);for(let t=0;t<n;++t)c[t]=t;for(let t=1;t<r;++t)c[t*n]=t;return o(t,e,s,i,c,1,n,""),i})(this._tree,t,e)}get(t){const e=h(this._tree,t);return void 0!==e?e.get(s):void 0}has(t){const e=h(this._tree,t);return void 0!==e&&e.has(s)}keys(){return new i(this,t)}set(t,e){if("string"!=typeof t)throw new Error("key must be a string");this._size=void 0;return u(this._tree,t).set(s,e),this}get size(){if(this._size)return this._size;this._size=0;const t=this.entries();for(;!t.next().done;)this._size+=1;return this._size}update(t,e){if("string"!=typeof t)throw new Error("key must be a string");this._size=void 0;const i=u(this._tree,t);return i.set(s,e(i.get(s))),this}fetch(t,e){if("string"!=typeof t)throw new Error("key must be a string");this._size=void 0;const i=u(this._tree,t);let n=i.get(s);return void 0===n&&i.set(s,n=e()),n}values(){return new i(this,e)}[Symbol.iterator](){return this.entries()}static from(t){const e=new r;for(const[s,i]of t)e.set(s,i);return e}static fromObject(t){return r.from(Object.entries(t))}}const c=(t,e,i=[])=>{if(0===e.length||null==t)return[t,i];for(const n of t.keys())if(n!==s&&e.startsWith(n))return i.push([t,n]),c(t.get(n),e.slice(n.length),i);return i.push([t,e]),c(void 0,"",i)},h=(t,e)=>{if(0===e.length||null==t)return t;for(const i of t.keys())if(i!==s&&e.startsWith(i))return h(t.get(i),e.slice(i.length))},u=(t,e)=>{const i=e.length;t:for(let n=0;t&&n<i;){for(const o of t.keys())if(o!==s&&e[n]===o[0]){const s=Math.min(i-n,o.length);let r=1;for(;r<s&&e[n+r]===o[r];)++r;const c=t.get(o);if(r===o.length)t=c;else{const s=new Map;s.set(o.slice(r),c),t.set(e.slice(n,n+r),s),t.delete(o),t=s}n+=r;continue t}const o=new Map;return t.set(e.slice(n),o),o}return t},d=(t,e)=>{const[i,n]=c(t,e);if(void 0!==i)if(i.delete(s),0===i.size)a(n);else if(1===i.size){const[t,e]=i.entries().next().value;l(n,t,e)}},a=t=>{if(0===t.length)return;const[e,i]=f(t);if(e.delete(i),0===e.size)a(t.slice(0,-1));else if(1===e.size){const[i,n]=e.entries().next().value;i!==s&&l(t.slice(0,-1),i,n)}},l=(t,e,s)=>{if(0===t.length)return;const[i,n]=f(t);i.set(n+e,s),i.delete(n)},f=t=>t[t.length-1],m="or",_="and",g="and_not";class p{constructor(t){if(null==(null==t?void 0:t.fields))throw new Error('MiniSearch: option "fields" must be provided');const e=null==t.autoVacuum||!0===t.autoVacuum?M:t.autoVacuum;this._options={...z,...t,autoVacuum:e,searchOptions:{...b,...t.searchOptions||{}},autoSuggestOptions:{...I,...t.autoSuggestOptions||{}}},this._index=new r,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldIds={},this._fieldLength=new Map,this._avgFieldLength=[],this._nextId=0,this._storedFields=new Map,this._dirtCount=0,this._currentVacuum=null,this._enqueuedVacuum=null,this._enqueuedVacuumConditions=F,this.addFields(this._options.fields)}add(t){const{extractField:e,stringifyField:s,tokenize:i,processTerm:n,fields:o,idField:r}=this._options,c=e(t,r);if(null==c)throw new Error(`MiniSearch: document does not have ID field "${r}"`);if(this._idToShortId.has(c))throw new Error(`MiniSearch: duplicate ID ${c}`);const h=this.addDocumentId(c);this.saveStoredFields(h,t);for(const r of o){const o=e(t,r);if(null==o)continue;const c=i(s(o,r),r),u=this._fieldIds[r],d=new Set(c).size;this.addFieldLength(h,u,this._documentCount-1,d);for(const t of c){const e=n(t,r);if(Array.isArray(e))for(const t of e)this.addTerm(u,h,t);else e&&this.addTerm(u,h,e)}}}addAll(t){for(const e of t)this.add(e)}addAllAsync(t,e={}){const{chunkSize:s=10}=e,i={chunk:[],promise:Promise.resolve()},{chunk:n,promise:o}=t.reduce((({chunk:t,promise:e},i,n)=>(t.push(i),(n+1)%s==0?{chunk:[],promise:e.then((()=>new Promise((t=>setTimeout(t,0))))).then((()=>this.addAll(t)))}:{chunk:t,promise:e})),i);return o.then((()=>this.addAll(n)))}remove(t){const{tokenize:e,processTerm:s,extractField:i,stringifyField:n,fields:o,idField:r}=this._options,c=i(t,r);if(null==c)throw new Error(`MiniSearch: document does not have ID field "${r}"`);const h=this._idToShortId.get(c);if(null==h)throw new Error(`MiniSearch: cannot remove document with ID ${c}: it is not in the index`);for(const r of o){const o=i(t,r);if(null==o)continue;const c=e(n(o,r),r),u=this._fieldIds[r],d=new Set(c).size;this.removeFieldLength(h,u,this._documentCount,d);for(const t of c){const e=s(t,r);if(Array.isArray(e))for(const t of e)this.removeTerm(u,h,t);else e&&this.removeTerm(u,h,e)}}this._storedFields.delete(h),this._documentIds.delete(h),this._idToShortId.delete(c),this._fieldLength.delete(h),this._documentCount-=1}removeAll(t){if(t)for(const e of t)this.remove(e);else{if(arguments.length>0)throw new Error("Expected documents to be present. Omit the argument to remove all documents.");this._index=new r,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldLength=new Map,this._avgFieldLength=[],this._storedFields=new Map,this._nextId=0}}discard(t){const e=this._idToShortId.get(t);if(null==e)throw new Error(`MiniSearch: cannot discard document with ID ${t}: it is not in the index`);this._idToShortId.delete(t),this._documentIds.delete(e),this._storedFields.delete(e),(this._fieldLength.get(e)||[]).forEach(((t,s)=>{this.removeFieldLength(e,s,this._documentCount,t)})),this._fieldLength.delete(e),this._documentCount-=1,this._dirtCount+=1,this.maybeAutoVacuum()}maybeAutoVacuum(){if(!1===this._options.autoVacuum)return;const{minDirtFactor:t,minDirtCount:e,batchSize:s,batchWait:i}=this._options.autoVacuum;this.conditionalVacuum({batchSize:s,batchWait:i},{minDirtCount:e,minDirtFactor:t})}discardAll(t){const e=this._options.autoVacuum;try{this._options.autoVacuum=!1;for(const e of t)this.discard(e)}finally{this._options.autoVacuum=e}this.maybeAutoVacuum()}replace(t){const{idField:e,extractField:s}=this._options,i=s(t,e);this.discard(i),this.add(t)}vacuum(t={}){return this.conditionalVacuum(t)}conditionalVacuum(t,e){return this._currentVacuum?(this._enqueuedVacuumConditions=this._enqueuedVacuumConditions&&e,null!=this._enqueuedVacuum||(this._enqueuedVacuum=this._currentVacuum.then((()=>{const e=this._enqueuedVacuumConditions;return this._enqueuedVacuumConditions=F,this.performVacuuming(t,e)}))),this._enqueuedVacuum):!1===this.vacuumConditionsMet(e)?Promise.resolve():(this._currentVacuum=this.performVacuuming(t),this._currentVacuum)}async performVacuuming(t,e){const s=this._dirtCount;if(this.vacuumConditionsMet(e)){const e=t.batchSize||S.batchSize,i=t.batchWait||S.batchWait;let n=1;for(const[t,s]of this._index){for(const[t,e]of s)for(const[i]of e)this._documentIds.has(i)||(e.size<=1?s.delete(t):e.delete(i));0===this._index.get(t).size&&this._index.delete(t),n%e==0&&await new Promise((t=>setTimeout(t,i))),n+=1}this._dirtCount-=s}await null,this._currentVacuum=this._enqueuedVacuum,this._enqueuedVacuum=null}vacuumConditionsMet(t){if(null==t)return!0;let{minDirtCount:e,minDirtFactor:s}=t;return e=e||M.minDirtCount,s=s||M.minDirtFactor,this.dirtCount>=e&&this.dirtFactor>=s}get isVacuuming(){return null!=this._currentVacuum}get dirtCount(){return this._dirtCount}get dirtFactor(){return this._dirtCount/(1+this._documentCount+this._dirtCount)}has(t){return this._idToShortId.has(t)}getStoredFields(t){const e=this._idToShortId.get(t);if(null!=e)return this._storedFields.get(e)}search(t,e={}){const{searchOptions:s}=this._options,i={...s,...e},n=this.executeQuery(t,e),o=[];for(const[t,{score:e,terms:s,match:r}]of n){const n=s.length||1,c={id:this._documentIds.get(t),score:e*n,terms:Object.keys(r),queryTerms:s,match:r};Object.assign(c,this._storedFields.get(t)),(null==i.filter||i.filter(c))&&o.push(c)}return t===p.wildcard&&null==i.boostDocument||o.sort(V),o}autoSuggest(t,e={}){e={...this._options.autoSuggestOptions,...e};const s=new Map;for(const{score:i,terms:n}of this.search(t,e)){const t=n.join(" "),e=s.get(t);null!=e?(e.score+=i,e.count+=1):s.set(t,{score:i,terms:n,count:1})}const i=[];for(const[t,{score:e,terms:n,count:o}]of s)i.push({suggestion:t,terms:n,score:e/o});return i.sort(V),i}get documentCount(){return this._documentCount}get termCount(){return this._index.size}static loadJSON(t,e){if(null==e)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJS(JSON.parse(t),e)}static async loadJSONAsync(t,e){if(null==e)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJSAsync(JSON.parse(t),e)}static getDefault(t){if(z.hasOwnProperty(t))return w(z,t);throw new Error(`MiniSearch: unknown option "${t}"`)}static loadJS(t,e){const{index:s,documentIds:i,fieldLength:n,storedFields:o,serializationVersion:r}=t,c=this.instantiateMiniSearch(t,e);c._documentIds=T(i),c._fieldLength=T(n),c._storedFields=T(o);for(const[t,e]of c._documentIds)c._idToShortId.set(e,t);for(const[t,e]of s){const s=new Map;for(const t of Object.keys(e)){let i=e[t];1===r&&(i=i.ds),s.set(parseInt(t,10),T(i))}c._index.set(t,s)}return c}static async loadJSAsync(t,e){const{index:s,documentIds:i,fieldLength:n,storedFields:o,serializationVersion:r}=t,c=this.instantiateMiniSearch(t,e);c._documentIds=await L(i),c._fieldLength=await L(n),c._storedFields=await L(o);for(const[t,e]of c._documentIds)c._idToShortId.set(e,t);let h=0;for(const[t,e]of s){const s=new Map;for(const t of Object.keys(e)){let i=e[t];1===r&&(i=i.ds),s.set(parseInt(t,10),await L(i))}++h%1e3==0&&await E(0),c._index.set(t,s)}return c}static instantiateMiniSearch(t,e){const{documentCount:s,nextId:i,fieldIds:n,averageFieldLength:o,dirtCount:c,serializationVersion:h}=t;if(1!==h&&2!==h)throw new Error("MiniSearch: cannot deserialize an index created with an incompatible version");const u=new p(e);return u._documentCount=s,u._nextId=i,u._idToShortId=new Map,u._fieldIds=n,u._avgFieldLength=o,u._dirtCount=c||0,u._index=new r,u}executeQuery(t,e={}){if(t===p.wildcard)return this.executeWildcardQuery(e);if("string"!=typeof t){const s={...e,...t,queries:void 0},i=t.queries.map((t=>this.executeQuery(t,s)));return this.combineResults(i,s.combineWith)}const{tokenize:s,processTerm:i,searchOptions:n}=this._options,o={tokenize:s,processTerm:i,...n,...e},{tokenize:r,processTerm:c}=o,h=r(t).flatMap((t=>c(t))).filter((t=>!!t)).map(x(o)).map((t=>this.executeQuerySpec(t,o)));return this.combineResults(h,o.combineWith)}executeQuerySpec(t,e){const s={...this._options.searchOptions,...e},i=(s.fields||this._options.fields).reduce(((t,e)=>({...t,[e]:w(s.boost,e)||1})),{}),{boostDocument:n,weights:o,maxFuzzy:r,bm25:c}=s,{fuzzy:h,prefix:u}={...b.weights,...o},d=this._index.get(t.term),a=this.termResults(t.term,t.term,1,t.termBoost,d,i,n,c);let l,f;if(t.prefix&&(l=this._index.atPrefix(t.term)),t.fuzzy){const e=!0===t.fuzzy?.2:t.fuzzy,s=e<1?Math.min(r,Math.round(t.term.length*e)):e;s&&(f=this._index.fuzzyGet(t.term,s))}if(l)for(const[e,s]of l){const o=e.length-t.term.length;if(!o)continue;null==f||f.delete(e);const r=u*e.length/(e.length+.3*o);this.termResults(t.term,e,r,t.termBoost,s,i,n,c,a)}if(f)for(const e of f.keys()){const[s,o]=f.get(e);if(!o)continue;const r=h*e.length/(e.length+o);this.termResults(t.term,e,r,t.termBoost,s,i,n,c,a)}return a}executeWildcardQuery(t){const e=new Map,s={...this._options.searchOptions,...t};for(const[t,i]of this._documentIds){const n=s.boostDocument?s.boostDocument(i,"",this._storedFields.get(t)):1;e.set(t,{score:n,terms:[],match:{}})}return e}combineResults(t,e=m){if(0===t.length)return new Map;const s=e.toLowerCase(),i=y[s];if(!i)throw new Error(`Invalid combination operator: ${e}`);return t.reduce(i)||new Map}toJSON(){const t=[];for(const[e,s]of this._index){const i={};for(const[t,e]of s)i[t]=Object.fromEntries(e);t.push([e,i])}return{documentCount:this._documentCount,nextId:this._nextId,documentIds:Object.fromEntries(this._documentIds),fieldIds:this._fieldIds,fieldLength:Object.fromEntries(this._fieldLength),averageFieldLength:this._avgFieldLength,storedFields:Object.fromEntries(this._storedFields),dirtCount:this._dirtCount,index:t,serializationVersion:2}}termResults(t,e,s,i,n,o,r,c,h=new Map){if(null==n)return h;for(const u of Object.keys(o)){const d=o[u],a=this._fieldIds[u],l=n.get(a);if(null==l)continue;let f=l.size;const m=this._avgFieldLength[a];for(const n of l.keys()){if(!this._documentIds.has(n)){this.removeTerm(a,n,e),f-=1;continue}const o=r?r(this._documentIds.get(n),e,this._storedFields.get(n)):1;if(!o)continue;const _=l.get(n),g=this._fieldLength.get(n)[a],p=s*i*d*o*v(_,f,this._documentCount,g,m,c),y=h.get(n);if(y){y.score+=p,k(y.terms,t);const s=w(y.match,e);s?s.push(u):y.match[e]=[u]}else h.set(n,{score:p,terms:[t],match:{[e]:[u]}})}}return h}addTerm(t,e,s){const i=this._index.fetch(s,O);let n=i.get(t);if(null==n)n=new Map,n.set(e,1),i.set(t,n);else{const t=n.get(e);n.set(e,(t||0)+1)}}removeTerm(t,e,s){if(!this._index.has(s))return void this.warnDocumentChanged(e,t,s);const i=this._index.fetch(s,O),n=i.get(t);null==n||null==n.get(e)?this.warnDocumentChanged(e,t,s):n.get(e)<=1?n.size<=1?i.delete(t):n.delete(e):n.set(e,n.get(e)-1),0===this._index.get(s).size&&this._index.delete(s)}warnDocumentChanged(t,e,s){for(const i of Object.keys(this._fieldIds))if(this._fieldIds[i]===e)return void this._options.logger("warn",`MiniSearch: document with ID ${this._documentIds.get(t)} has changed before removal: term "${s}" was not present in field "${i}". Removing a document after it has changed can corrupt the index!`,"version_conflict")}addDocumentId(t){const e=this._nextId;return this._idToShortId.set(t,e),this._documentIds.set(e,t),this._documentCount+=1,this._nextId+=1,e}addFields(t){for(let e=0;e<t.length;e++)this._fieldIds[t[e]]=e}addFieldLength(t,e,s,i){let n=this._fieldLength.get(t);null==n&&this._fieldLength.set(t,n=[]),n[e]=i;const o=(this._avgFieldLength[e]||0)*s+i;this._avgFieldLength[e]=o/(s+1)}removeFieldLength(t,e,s,i){if(1===s)return void(this._avgFieldLength[e]=0);const n=this._avgFieldLength[e]*s-i;this._avgFieldLength[e]=n/(s-1)}saveStoredFields(t,e){const{storeFields:s,extractField:i}=this._options;if(null==s||0===s.length)return;let n=this._storedFields.get(t);null==n&&this._storedFields.set(t,n={});for(const t of s){const s=i(e,t);void 0!==s&&(n[t]=s)}}}p.wildcard=Symbol("*");const w=(t,e)=>Object.prototype.hasOwnProperty.call(t,e)?t[e]:void 0,y={[m]:(t,e)=>{for(const s of e.keys()){const i=t.get(s);if(null==i)t.set(s,e.get(s));else{const{score:t,terms:n,match:o}=e.get(s);i.score=i.score+t,i.match=Object.assign(i.match,o),C(i.terms,n)}}return t},[_]:(t,e)=>{const s=new Map;for(const i of e.keys()){const n=t.get(i);if(null==n)continue;const{score:o,terms:r,match:c}=e.get(i);C(n.terms,r),s.set(i,{score:n.score+o,terms:n.terms,match:Object.assign(n.match,c)})}return s},[g]:(t,e)=>{for(const s of e.keys())t.delete(s);return t}},v=(t,e,s,i,n,o)=>{const{k:r,b:c,d:h}=o;return Math.log(1+(s-e+.5)/(e+.5))*(h+t*(r+1)/(t+r*(1-c+c*i/n)))},x=t=>(e,s,i)=>({term:e,fuzzy:"function"==typeof t.fuzzy?t.fuzzy(e,s,i):t.fuzzy||!1,prefix:"function"==typeof t.prefix?t.prefix(e,s,i):!0===t.prefix,termBoost:"function"==typeof t.boostTerm?t.boostTerm(e,s,i):1}),z={idField:"id",extractField:(t,e)=>t[e],stringifyField:(t,e)=>t.toString(),tokenize:t=>t.split(D),processTerm:t=>t.toLowerCase(),fields:void 0,searchOptions:void 0,storeFields:[],logger:(t,e)=>{"function"==typeof(null===console||void 0===console?void 0:console[t])&&console[t](e)},autoVacuum:!0},b={combineWith:m,prefix:!1,fuzzy:!1,maxFuzzy:6,boost:{},weights:{fuzzy:.45,prefix:.375},bm25:{k:1.2,b:.7,d:.5}},I={combineWith:"and",prefix:(t,e,s)=>e===s.length-1},S={batchSize:1e3,batchWait:10},F={minDirtFactor:.1,minDirtCount:20},M={...S,...F},k=(t,e)=>{t.includes(e)||t.push(e)},C=(t,e)=>{for(const s of e)t.includes(s)||t.push(s)},V=({score:t},{score:e})=>e-t,O=()=>new Map,T=t=>{const e=new Map;for(const s of Object.keys(t))e.set(parseInt(s,10),t[s]);return e},L=async t=>{const e=new Map;let s=0;for(const i of Object.keys(t))e.set(parseInt(i,10),t[i]),++s%1e3==0&&await E(0);return e},E=t=>new Promise((e=>setTimeout(e,t))),D=/[\n\r\p{Z}\p{P}]+/u;return p}));
|
|
8
|
+
//# sourceMappingURL=/sm/1e4d3c573a14b2678ad5755d10141fbd85cba14ae591344bb984b98576ea9860.map
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
class RailsIconPreview extends HTMLElement {
|
|
2
|
+
connectedCallback() {
|
|
3
|
+
this.#render()
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
get searchData() {
|
|
7
|
+
return {
|
|
8
|
+
id: this.#name,
|
|
9
|
+
name: this.#name,
|
|
10
|
+
tags: this.getAttribute('tags') || ''
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#render() {
|
|
15
|
+
this.innerHTML = `
|
|
16
|
+
<section>
|
|
17
|
+
<div class='svg'>${this.#svgIcon}</div>
|
|
18
|
+
<p title='${this.#name}'>${this.#name}</p>
|
|
19
|
+
|
|
20
|
+
<div class='actions'>
|
|
21
|
+
<button data-action='copy-name'>Copy name</button>
|
|
22
|
+
|
|
23
|
+
<button data-action='copy-helper'>Copy helper</button>
|
|
24
|
+
</div>
|
|
25
|
+
</section>
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
this.querySelector('[data-action="copy-name"]').onclick = () => this.#copyName()
|
|
29
|
+
this.querySelector('[data-action="copy-helper"]').onclick = () => this.#copyHelper()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#copyName() {
|
|
33
|
+
navigator.clipboard.writeText(this.#name)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#copyHelper() {
|
|
37
|
+
const parts = [`icon('${this.#name}'`]
|
|
38
|
+
|
|
39
|
+
if (this.#library !== this.#defaultLibrary) {
|
|
40
|
+
parts.push(`library: :${this.#library}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (this.#variant && this.#variant !== this.#defaultVariant) {
|
|
44
|
+
parts.push(`variant: :${this.#variant}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const helper = parts.length > 1 ? `${parts[0]}, ${parts.slice(1).join(', ')})` : `${parts[0]})`
|
|
48
|
+
navigator.clipboard.writeText(helper)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get #name() {
|
|
52
|
+
return this.getAttribute('name')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get #library() {
|
|
56
|
+
return this.getAttribute('library')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get #variant() {
|
|
60
|
+
return this.getAttribute('variant')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get #defaultLibrary() {
|
|
64
|
+
return this.getAttribute('default-library')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get #defaultVariant() {
|
|
68
|
+
return this.getAttribute('default-variant')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get #svgIcon() {
|
|
72
|
+
return this.innerHTML
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
customElements.define('rails-icon-preview', RailsIconPreview)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class IconSearch {
|
|
80
|
+
constructor() {
|
|
81
|
+
this.searchInput = document.querySelector('input[type="search"]')
|
|
82
|
+
this.resultCount = document.querySelector('.result-count')
|
|
83
|
+
this.icons = Array.from(document.querySelectorAll('rails-icon-preview'))
|
|
84
|
+
this.totalCount = this.icons.length
|
|
85
|
+
|
|
86
|
+
this.miniSearch = new MiniSearch({
|
|
87
|
+
fields: ['name', 'tags'],
|
|
88
|
+
storeFields: ['id'],
|
|
89
|
+
searchOptions: {
|
|
90
|
+
fuzzy: false,
|
|
91
|
+
prefix: false
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
this.#indexIcons()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getIconList() {
|
|
99
|
+
return document.querySelector('ul.icons')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getVisibleIcons() {
|
|
103
|
+
return this.icons.filter(icon => icon.closest('li').style.display !== 'none')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#indexIcons() {
|
|
107
|
+
const documents = this.icons.map(icon => icon.searchData)
|
|
108
|
+
|
|
109
|
+
this.miniSearch.addAll(documents)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
filterIcons(query) {
|
|
113
|
+
if (!query) {
|
|
114
|
+
this.icons.forEach(icon => {
|
|
115
|
+
icon.closest('li').style.display = ''
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
this.#updateResultCount(this.totalCount)
|
|
119
|
+
this.#showNoResults(false)
|
|
120
|
+
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const results = this.miniSearch.search(query)
|
|
125
|
+
const resultIds = new Set(results.map(result => result.id))
|
|
126
|
+
let visibleCount = 0
|
|
127
|
+
|
|
128
|
+
this.icons.forEach(icon => {
|
|
129
|
+
const listItem = icon.closest('li')
|
|
130
|
+
|
|
131
|
+
if (resultIds.has(icon.searchData.id)) {
|
|
132
|
+
listItem.style.display = ''
|
|
133
|
+
|
|
134
|
+
visibleCount++
|
|
135
|
+
} else {
|
|
136
|
+
listItem.style.display = 'none'
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
this.#updateResultCount(visibleCount)
|
|
141
|
+
this.#showNoResults(visibleCount === 0, query)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#updateResultCount(count) {
|
|
145
|
+
if (this.resultCount) {
|
|
146
|
+
this.resultCount.textContent = count === this.totalCount
|
|
147
|
+
? ''
|
|
148
|
+
: `${count} of ${this.totalCount} icons`
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#showNoResults(show, query = '') {
|
|
153
|
+
const noResults = document.querySelector('.no-results')
|
|
154
|
+
const iconsList = document.querySelector('ul.icons')
|
|
155
|
+
|
|
156
|
+
if (show) {
|
|
157
|
+
noResults.removeAttribute('hidden')
|
|
158
|
+
noResults.querySelector('.query').textContent = query
|
|
159
|
+
|
|
160
|
+
iconsList.setAttribute('hidden', 'hidden')
|
|
161
|
+
} else {
|
|
162
|
+
noResults.setAttribute('hidden', 'hidden')
|
|
163
|
+
|
|
164
|
+
iconsList.removeAttribute('hidden')
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class UrlSync {
|
|
171
|
+
constructor({ onSearch }) {
|
|
172
|
+
this.onSearch = onSearch
|
|
173
|
+
|
|
174
|
+
this.#loadFromUrl()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#loadFromUrl() {
|
|
178
|
+
const params = new URLSearchParams(window.location.search)
|
|
179
|
+
const query = params.get('q')
|
|
180
|
+
|
|
181
|
+
if (query) {
|
|
182
|
+
this.onSearch?.(query)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
updateUrl(query) {
|
|
187
|
+
const url = new URL(window.location)
|
|
188
|
+
|
|
189
|
+
if (query) {
|
|
190
|
+
url.searchParams.set('q', query)
|
|
191
|
+
} else {
|
|
192
|
+
url.searchParams.delete('q')
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
history.replaceState(null, '', url)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class KeyboardController {
|
|
201
|
+
constructor({ searchInput, iconList, iconSearch, onFilter, onClearFocus, onActivate }) {
|
|
202
|
+
this.searchInput = searchInput
|
|
203
|
+
this.iconList = iconList
|
|
204
|
+
this.iconSearch = iconSearch
|
|
205
|
+
|
|
206
|
+
this.onFilter = onFilter
|
|
207
|
+
this.onClearFocus = onClearFocus
|
|
208
|
+
this.onActivate = onActivate
|
|
209
|
+
|
|
210
|
+
this.focusedIndex = -1
|
|
211
|
+
|
|
212
|
+
this.#setup()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#setup() {
|
|
216
|
+
this.#setupGlobalKeys()
|
|
217
|
+
this.#setupSearchInputKeys()
|
|
218
|
+
this.#setupIconListKeys()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#setupGlobalKeys() {
|
|
222
|
+
document.addEventListener('keydown', (event) => {
|
|
223
|
+
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') return
|
|
224
|
+
|
|
225
|
+
if (event.key === '/') {
|
|
226
|
+
event.preventDefault()
|
|
227
|
+
|
|
228
|
+
this.searchInput?.focus()
|
|
229
|
+
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (event.key.length === 1 && /[a-zA-Z0-9]/.test(event.key)) {
|
|
234
|
+
this.searchInput?.focus()
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#setupSearchInputKeys() {
|
|
240
|
+
this.searchInput?.addEventListener('keydown', (event) => {
|
|
241
|
+
switch (event.key) {
|
|
242
|
+
case 'Escape':
|
|
243
|
+
this.searchInput.value = ''
|
|
244
|
+
this.searchInput.blur()
|
|
245
|
+
this.onFilter('')
|
|
246
|
+
this.#clearFocus()
|
|
247
|
+
|
|
248
|
+
break
|
|
249
|
+
case 'ArrowDown':
|
|
250
|
+
event.preventDefault()
|
|
251
|
+
|
|
252
|
+
this.#moveFocus(1)
|
|
253
|
+
|
|
254
|
+
break
|
|
255
|
+
case 'ArrowUp':
|
|
256
|
+
event.preventDefault()
|
|
257
|
+
|
|
258
|
+
this.#moveFocus(-1)
|
|
259
|
+
|
|
260
|
+
break
|
|
261
|
+
case 'Enter':
|
|
262
|
+
if (this.focusedIndex >= 0) {
|
|
263
|
+
this.#activateFocused()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
break
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
this.searchInput?.addEventListener('input', (event) => {
|
|
271
|
+
this.#clearFocus()
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
#setupIconListKeys() {
|
|
276
|
+
this.iconList?.addEventListener('keydown', (event) => {
|
|
277
|
+
switch (event.key) {
|
|
278
|
+
case 'ArrowDown':
|
|
279
|
+
event.preventDefault()
|
|
280
|
+
|
|
281
|
+
this.#moveFocus(1)
|
|
282
|
+
|
|
283
|
+
break
|
|
284
|
+
case 'ArrowUp':
|
|
285
|
+
event.preventDefault()
|
|
286
|
+
|
|
287
|
+
this.#moveFocus(-1)
|
|
288
|
+
|
|
289
|
+
break
|
|
290
|
+
case 'Enter':
|
|
291
|
+
case ' ':
|
|
292
|
+
if (this.focusedIndex >= 0) {
|
|
293
|
+
event.preventDefault()
|
|
294
|
+
|
|
295
|
+
this.#activateFocused()
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
break
|
|
299
|
+
case 'Escape':
|
|
300
|
+
this.#clearFocus()
|
|
301
|
+
|
|
302
|
+
this.searchInput?.focus()
|
|
303
|
+
|
|
304
|
+
break
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
setFocusedIndex(index) {
|
|
310
|
+
this.focusedIndex = index
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#moveFocus(direction) {
|
|
314
|
+
const icons = this.iconSearch?.getVisibleIcons() || []
|
|
315
|
+
if (icons.length === 0) return
|
|
316
|
+
|
|
317
|
+
this.focusedIndex += direction
|
|
318
|
+
|
|
319
|
+
if (this.focusedIndex < 0) {
|
|
320
|
+
this.focusedIndex = icons.length - 1
|
|
321
|
+
} else if (this.focusedIndex >= icons.length) {
|
|
322
|
+
this.focusedIndex = 0
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.#updateFocus(icons)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#updateFocus(icons) {
|
|
329
|
+
icons.forEach((icon, index) => {
|
|
330
|
+
icon.closest('li').classList.toggle('focused', index === this.focusedIndex)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
if (icons[this.focusedIndex]) {
|
|
334
|
+
icons[this.focusedIndex].closest('li').scrollIntoView({ block: 'nearest' })
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#clearFocus() {
|
|
339
|
+
this.focusedIndex = -1
|
|
340
|
+
|
|
341
|
+
const icons = this.iconSearch?.icons || []
|
|
342
|
+
|
|
343
|
+
icons.forEach(icon => icon.closest('li').classList.remove('focused'))
|
|
344
|
+
|
|
345
|
+
this.onClearFocus?.()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
#activateFocused() {
|
|
349
|
+
const icons = this.iconSearch?.getVisibleIcons() || []
|
|
350
|
+
const focusedIcon = icons[this.focusedIndex]
|
|
351
|
+
|
|
352
|
+
if (focusedIcon) {
|
|
353
|
+
focusedIcon.querySelector('[data-action="copy-helper"]')?.click()
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
360
|
+
const searchInput = document.querySelector('input[type="search"]')
|
|
361
|
+
const iconList = document.querySelector('ul.icons')
|
|
362
|
+
const iconSearch = new IconSearch()
|
|
363
|
+
|
|
364
|
+
const urlSync = new UrlSync({
|
|
365
|
+
onSearch: (query) => {
|
|
366
|
+
searchInput.value = query
|
|
367
|
+
|
|
368
|
+
iconSearch.filterIcons(query)
|
|
369
|
+
}
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
const keyboard = new KeyboardController({
|
|
373
|
+
searchInput,
|
|
374
|
+
iconList,
|
|
375
|
+
iconSearch,
|
|
376
|
+
|
|
377
|
+
onFilter: (query) => {
|
|
378
|
+
iconSearch.filterIcons(query)
|
|
379
|
+
urlSync.updateUrl(query)
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
onClearFocus: () => keyboard.setFocusedIndex(-1)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
searchInput?.addEventListener('input', (event) => {
|
|
386
|
+
const query = event.target.value.trim()
|
|
387
|
+
|
|
388
|
+
iconSearch.filterIcons(query)
|
|
389
|
+
|
|
390
|
+
urlSync.updateUrl(query)
|
|
391
|
+
})
|
|
392
|
+
})
|