rails_icons 1.7.1 → 1.8.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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +2 -0
  4. data/app/assets/javascripts/rails_icons/minisearch.min.js +8 -0
  5. data/app/assets/javascripts/rails_icons/preview/preview.js +392 -0
  6. data/app/assets/stylesheets/rails_icons/preview.css +169 -0
  7. data/app/controllers/rails_icons/previews_controller.rb +28 -0
  8. data/app/models/rails_icons/preview/tags/animated.yml +4 -0
  9. data/app/models/rails_icons/preview/tags/feather.yml +287 -0
  10. data/app/models/rails_icons/preview/tags/flags.yml +271 -0
  11. data/app/models/rails_icons/preview/tags/heroicons.yml +324 -0
  12. data/app/models/rails_icons/preview/tags/linear.yml +170 -0
  13. data/app/models/rails_icons/preview/tags/lucide.yml +1703 -0
  14. data/app/models/rails_icons/preview/tags/phosphor.yml +1512 -0
  15. data/app/models/rails_icons/preview/tags/radix.yml +332 -0
  16. data/app/models/rails_icons/preview/tags/sidekickicons.yml +58 -0
  17. data/app/models/rails_icons/preview/tags/tabler.yml +5021 -0
  18. data/app/models/rails_icons/preview/tags/weather.yml +219 -0
  19. data/app/models/rails_icons/preview/tags.rb +14 -0
  20. data/app/models/rails_icons/preview.rb +53 -0
  21. data/app/views/rails_icons/previews/show.html.erb +59 -0
  22. data/config/routes.rb +8 -0
  23. data/lib/generators/rails_icons/install_generator.rb +8 -0
  24. data/lib/rails_icons/generate_tags.rb +44 -0
  25. data/lib/rails_icons/version.rb +1 -1
  26. data/lib/ruby_lsp/rails_icons/addon.rb +2 -0
  27. metadata +22 -6
  28. data/app/assets/svg/rails_icons/icons/animated/bouncing-dots.svg +0 -1
  29. data/app/assets/svg/rails_icons/icons/animated/faded-spinner.svg +0 -1
  30. data/app/assets/svg/rails_icons/icons/animated/fading-dots.svg +0 -1
  31. 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: 53d052de970b40fa2b2d56b1312fcb8571c7bc22eaed1a8d7a4b222699ee6d68
4
- data.tar.gz: a67eb5e2314eb02a4372f336d69c8ee51306a3a21597a9b02523a74f09bf6dd4
3
+ metadata.gz: 9d05be67d1c13063b9c5fca713d8ef525f2859bc06052909d5bd2df13bbede6f
4
+ data.tar.gz: 37d0a613b5c7cbeac079203e5cd44337fd338f58f0e2b5ec8ad111cc4897e9b0
5
5
  SHA512:
6
- metadata.gz: 00deea25eab7ba7db2c90068d0f772e83ca4425144369111430a7001bbf6160ce684ad4f5fc38adcd9a586c85651ff3fe56c79bb3c53fecc854a2d5073c94f3d
7
- data.tar.gz: e188f7d1dd7958dcc1e16444f122299be1660b03a0b15f879f1ee8914639cfdd9727096dbcbbe78a16940aca9c0d7740174ce37793308902c5257ca241338d57
6
+ metadata.gz: 7d6fd9ff135d39bb5b02cb70e7a96b8a12d3f2f31c95c91cc0faab8280a87f54a97752254291bbe61083d409c7a125cef07d53f2ce7a0e2c4fceac28fbf11e66
7
+ data.tar.gz: fdcffc8c5dfcbb944aea21f64994989d207b968764c0e74f9ff59fd5b8cab4294e15a9e4c35fbce918d3e4011d9e17566a0b6ef57c944a9c560c747c5ab2fe25
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_icons (1.7.1)
4
+ rails_icons (1.8.0)
5
5
  icons (~> 0.8.1)
6
6
  rails (>= 7.0)
7
7
 
data/README.md CHANGED
@@ -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
 
@@ -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
+ })
@@ -0,0 +1,169 @@
1
+ body {
2
+ margin: 0;
3
+ padding: 2rem;
4
+ font-family: system-ui, sans-serif;
5
+ background-color: rgb(241 245 249 / .5);
6
+ }
7
+
8
+ ul { list-style: none; }
9
+
10
+ nav {
11
+ display: grid;
12
+ grid-template-columns: auto auto 1fr auto;
13
+ gap: 1rem;
14
+ align-items: center;
15
+ margin-bottom: 2rem;
16
+
17
+ select {
18
+ appearance: none;
19
+ padding: .5rem 2rem .5rem .75rem;
20
+ font-size: .875rem;
21
+ color: rgb(30 41 59);
22
+ border: 1px solid rgb(226 232 240);
23
+ border-radius: 0.5rem;
24
+ cursor: pointer;
25
+ background-color: rgb(255 255 255);
26
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
27
+ background-repeat: no-repeat;
28
+ background-position: right .5rem center;
29
+
30
+ &:hover {
31
+ border-color: rgb(203 213 225);
32
+ }
33
+
34
+ &:focus {
35
+ outline: none;
36
+ border-color: rgb(59 130 246);
37
+ box-shadow: 0 0 0 3px rgb(59 130 246 / .1);
38
+ }
39
+ }
40
+
41
+ .search-wrapper {
42
+ position: relative;
43
+ display: flex;
44
+ align-items: center;
45
+ max-width: 400px;
46
+ width: 100%;
47
+
48
+ input[type="search"] {
49
+ width: 100%;
50
+ padding: .5rem 3rem .5rem .75rem;
51
+ font-size: .875rem;
52
+ color: rgb(30 41 59);
53
+ background-color: rgb(255 255 255);
54
+ border: 1px solid rgb(226 232 240);
55
+ border-radius: .5rem;
56
+
57
+ &::placeholder { color: rgb(148 163 184); }
58
+
59
+ &:hover { border-color: rgb(203 213 225); }
60
+
61
+ &:focus {
62
+ outline: none;
63
+ border-color: rgb(59 130 246);
64
+ box-shadow: 0 0 0 3px rgb(59 130 246 / .1);
65
+ }
66
+
67
+ &::-webkit-search-cancel-button { display: none; }
68
+ }
69
+
70
+ kbd {
71
+ position: absolute;
72
+ right: .5rem;
73
+ padding: .125rem .375rem;
74
+ font-family: inherit;
75
+ font-size: .75rem;
76
+ color: rgb(148 163 184);
77
+ background-color: rgb(241 245 249);
78
+ border: 1px solid rgb(226 232 240);
79
+ border-radius: .25rem;
80
+ pointer-events: none;
81
+ }
82
+ }
83
+
84
+ .result-count {
85
+ min-width: 120px;
86
+ font-size: .875rem;
87
+ color: rgb(100 116 139 / .6);
88
+ }
89
+ }
90
+
91
+ .no-results {
92
+ padding: 2rem;
93
+ font-size: .875rem;
94
+ text-align: center;
95
+ color: rgb(100 116 139 / .6);
96
+ }
97
+
98
+ ul.icons {
99
+ margin: 0;
100
+ padding: 0;
101
+ display: grid;
102
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
103
+ gap: 1rem;
104
+
105
+ li { display: contents; }
106
+ }
107
+
108
+ rails-icon-preview {
109
+ section {
110
+ position: relative;
111
+ padding: 1rem .5rem;
112
+ background-color: rgb(255 255 255);
113
+ border-radius: .5rem;
114
+
115
+ .svg {
116
+ width: 32px;
117
+ margin-inline: auto;
118
+ aspect-ratio: 1 / 1;
119
+
120
+ svg { width: 100%; height: 100%; }
121
+ }
122
+
123
+ p {
124
+ margin: 0;
125
+ margin-block-start: .5rem;
126
+ font-size: .75rem;
127
+ color: rgb(100 116 139);
128
+ text-align: center;
129
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
130
+ }
131
+
132
+ .actions {
133
+ position: absolute;
134
+ inset: 0;
135
+ display: grid;
136
+ grid-template-rows: 1fr 1fr;
137
+ gap: .5rem;
138
+ padding: .5rem;
139
+ opacity: 0;
140
+ transition: opacity ease-in-out 100ms;
141
+
142
+ button {
143
+ padding: .25rem .125rem;
144
+ font-size: .75rem;
145
+ font-weight: 500;
146
+ color: rgb(30 41 59);
147
+ background-color: rgb(226 232 240 / .5);
148
+ border: 1px solid rgb(226 232 240 / .5);
149
+ border-radius: 9999px;
150
+ cursor: pointer;
151
+ -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
152
+
153
+ &:hover {
154
+ border: 1px solid rgb(226 232 240 / .9);
155
+ }
156
+ }
157
+ }
158
+
159
+ &:hover .actions,
160
+ &:focus-within .actions {
161
+ opacity: 1;
162
+ }
163
+ }
164
+ }
165
+
166
+ ul.icons li.focused section {
167
+ outline: 2px solid rgb(59 130 246);
168
+ outline-offset: 2px;
169
+ }
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsIcons
4
+ class PreviewsController < ActionController::Base
5
+ def show
6
+ @libraries = preview.libraries
7
+ @library = params[:library] || RailsIcons.configuration.default_library || @libraries.first
8
+ @default_library = RailsIcons.configuration.default_library
9
+ @default_variant = library_default_variant
10
+ @variant = params[:variant] || @default_variant
11
+ @variants = preview.variants(@library)
12
+
13
+ @icon_names = preview.icon_names(@library, @variant)
14
+ @tags = preview.tags(@library, @variant)
15
+ end
16
+
17
+ private
18
+
19
+ def preview
20
+ @preview ||= Preview.new
21
+ end
22
+
23
+ def library_default_variant
24
+ RailsIcons.configuration.libraries[@library.to_sym]&.default_variant ||
25
+ RailsIcons.configuration.default_variant
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,4 @@
1
+ bouncing-dots: loading spinner dots animation wait
2
+ faded-spinner: loading spinner circle animation wait
3
+ fading-dots: loading dots animation wait ellipsis
4
+ trailing-spinner: loading spinner animation wait