npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@brftech/filex

v0.51.0

Published

filex Web Component - drop-in <filex-explorer> custom element (Vue 3 runtime bundled)

Downloads

6,981

Readme

@brftech/filex

Drop-in <filex-explorer> Web Component for the filex file manager. Wraps the Vue 3 <FileExplorer> SFC from @brftech/filex-core and ships with the Vue runtime bundled in - load it from any CDN, embed in any framework, no peers required.

Install

npm

npm i @brftech/filex
// Side-effect import registers the element.
import '@brftech/filex';

CDN (no build step)

<script type="module" src="https://cdn.jsdelivr.net/npm/@brftech/filex/dist/filex.js"></script>

Styles

No CSS import, no <link>. The bundle carries the explorer's stylesheet inside the JavaScript and appends it to <head> once, the first time an element mounts - so the two lines above are all a page needs, whether they come from a CDN or a bundler. It is @brftech/filex-core's sheet byte for byte, which is what keeps this distribution and the Vue one looking the same.

A style.css is published as well, for a host that would rather serve the sheet itself:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@brftech/filex/dist/style.css">

The look is the --fe-* custom properties; set them on any scope above the element (:root, a wrapper div) and the explorer follows. The element deliberately has no shadow root for exactly this reason - the host page's tokens, dark mode and fonts reach inside. The consequence is that a host cannot fill a slot in it either, so the product mark in the top bar comes from config.brand ({ name, markUrl }).

Use

Plain HTML

<filex-explorer
  api-base="https://files.example.com"
  locale="tr"
  theme="auto"
></filex-explorer>

<script type="module">
  const el = document.querySelector('filex-explorer');
  // ⚠ Configure BEFORE the element is registered. A static `import` would be
  // hoisted above these lines, mount the explorer with no credentials and
  // spend its first listing on a request that can only fail.
  el.config = {
    apiBase: 'https://files.example.com',
    auth: { kind: 'bearer', token: '<jwt>' },
    rootPath: 'main://projects/acme',
  };
  // ⚠ The payload is e.detail[0] - see Events below.
  el.addEventListener('error', (e) => console.error(e.detail[0].message));
  el.addEventListener('share-created', (e) => navigator.clipboard.writeText(e.detail[0].url));
  await import('@brftech/filex');   // registers <filex-explorer>
</script>

Inside a non-Vue framework

The element is just a normal DOM custom element - Angular, Svelte, plain JS, no problem.

<filex-connections> - reaching the server without a browser

The package registers a second element. filex can be spoken to as S3, SFTP, FTPS, NFSv3 and WebDAV, and mounted with filex mount; this is where a user mints the credential each protocol takes and reads instructions built from that deployment rather than a template with angle brackets in it. It is the same component the filex admin panel and the filex desktop app render - there is no second set of instructions anywhere.

Since v0.43.0 this element is the "how to connect" surface only. The initial-tab attribute and the changed event are gone with the Storages tab: it does not create, edit or delete a storage. Storages are managed in the filex admin panel.

<filex-connections></filex-connections>

<script type="module">
  import '@brftech/filex';
  const el = document.querySelector('filex-connections');
  el.setAttribute('closable', '');     // show a close button, emits `close`
  el.config = {
    apiBase: 'https://files.example.com',
    auth: { kind: 'bearer', token: '<jwt>' },
    locale: 'tr',
  };
  el.addEventListener('error', (e) => console.error(e.detail));
</script>

⚠⚠ Configure it through the config property, not attributes - el.config = { ...el.config, locale: 'tr' }. buildConfig merges {...attributes, ...config} and the config object wins, so an attribute is only ever a fallback for a key the config does not carry. That exact mistake shipped in v0.19.0: the shell went Turkish while the file list stayed English, and the element reported locale === 'tr' the whole time.

See docs/PROTOCOLS.md.

Attributes

Simple attributes are auto-parsed into the underlying config prop:

| Attribute | Maps to | |---|---| | api-base | config.apiBase | | endpoint | config.endpoint (legacy Vuefinder-compat) | | locale | config.locale - en, tr, or any language the server has a language pack for (pt-br, es, ar, …). A right-to-left language lays this element out right to left, from its own locale rather than the host page's. | | theme | config.theme (light / dark / auto) | | trash-visible | config.trashVisible | | sidenav | config.sideNav - the navigation panel (the "+ New" menu · Home / Shared with me / My shares / Recent / Starred / Trash · your tags in two groups, personal and team · an Apps section, one row per installed app's own page · the storages this caller can reach). Present or ="true" is on, ="false" off; absent keeps the default, which is on. | | connections | config.connections - the panel's "How to connect" and "API keys" entries. Default on, except under ui-profile="simple" where it is off. ⚠ "API keys" is additionally dropped when the caller is an app token - see config.callerKind below. | | ui-profile | config.uiProfile - "standard" (default) or "simple" (one pane, list/grid only, no tab strip, no split pane, "How to connect"/"API keys" off). Two values, no third: any other string resolves to "standard" and logs one console line naming it, so the "drive" profile that was removed after v0.40.0 no longer reduces anything - pass "simple" instead. ⚠⚠ It does not decide the look: the "+ New" menu, the one wide header search field with its ⌘K chip, the Type/People/Modified/Size filter row, the Folders/Files sections in grid (replaced by date headings while sorted by Modified), Details/Activity in the info panel and the storage line are what every embed draws now, with no string passed. |

For anything richer (auth, custom endpoints, share base, …) set the config JS property after element creation. Properties merge on top of attributes.

config.callerKind - "user" (a person) or "app" (an integration: a host app's proxy, a bot). "app" leaves out the surfaces that belong to one identity: API keys, Recent, Starred, Shared with me. Upload, the storages, Trash and "How to connect" stay, so an embed is still an embed. Omit it and the element asks the server (GET /api/files/capabilities → caller_kind), which is authoritative because only the server knows a token's kind; set it when your page already knows, to spare the moment before that answer lands. ⚠ Proxying every visitor with one shared API token IS the "app" case - see docs/MCP.md.

Events

Native CustomEvents - listen with addEventListener. ⚠ The SFC payload is event.detail[0], not event.detail: Vue dispatches a custom element's emit as new CustomEvent(name, { detail: args }), where args is the emit's argument list. event.detail.url is therefore undefined, and for selection-change - whose payload is itself an array - event.detail.length is always 1.

| Event | Payload - event.detail[0] | |---|---| | error | { message, context? } | | share-created | { path, url, pin } | | file-opened | { path, basename } | | upload-progress | { uploadId, percent, done } | | selection-change | Array<{ path, basename, type }> |

Build

pnpm build      # vue-tsc + vite lib build → dist/filex.js + dist/style.css

⚠⚠ sideEffects in package.json has to keep matching every file the build emits (./dist/*.js, ./dist/*.cjs), not a list of names. The entry is only a re-export: customElements.define and the stylesheet injection live in the hashed chunk beside it, and a list that does not cover the chunk lets a bundler drop it - the element is then never registered, the sheet never injected, and a React or Vite host renders an empty page with no error anywhere. web/tests/deploy/packageLook.test.ts is the guard.

License

MIT