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

@repobit/dex-store-elements

v1.4.2

Published

HTML elements layer for pricings

Readme

@repobit/dex-store-elements

Lightweight HTML custom elements + attribute renderers for building dynamic pricing UIs on top of @repobit/dex-store.

  • Custom elements: <bd-root>, <bd-product>, <bd-option>, <bd-state>, <bd-context>
  • Unified attribute-based renderers (no framework required)
  • Eta templates for text/HTML and attributes
  • Single, merged data context across product, option and state
  • Extensible “derived” variables/functions you can compute and use anywhere

Requirements

  • Node 18+

Install

npm i @repobit/dex-store-elements @repobit/dex-store

Peer dependencies are resolved automatically by npm; no extra install command is required.

Quick start

<!-- index.html -->
<script type="module">
  import { registerContextNodes, registerActionNodes, registerRenderNodes } from '@repobit/dex-store-elements';
  import { Store } from '@repobit/dex-store';

  window.addEventListener('DOMContentLoaded', async () => {
    registerContextNodes();
    const root = document.querySelector('bd-root');
    root.store = new Store({
      locale: 'en-us',
      provider: { name: 'vlaicu' }
    });

    // Optional: analytics data layer callback
    // Fires once per <bd-option> that sets `data-layer-event`
    root.dataLayer = ({ option, event }) => {
      // Example GTM-style push; adapt fields as needed
      window.dataLayer?.push({
        event,
        productId   : option.getProduct().getId(),
        campaign    : option.getProduct().getCampaign(),
        variation   : option.getVariation(), // e.g. "5-12"
        devices     : option.getDevices(),
        subscription: option.getSubscription(),
        price       : option.getDiscountedPrice({ currency: false })
      });
    };

    // Optional: define derived values/functions for templates + hide DSL
    root.derived = async ({ option }) => ({
      mails: (p) => ((option?.getDevices?.() ?? 0) / p) * 100
    });

    registerActionNodes(root);
    registerRenderNodes(root);
  });
</script>

<bd-root store-name="root">
  <bd-product store-name="product" product-id="com.bitdefender.tsmd.v2">
    <bd-option devices="5" subscription="12" data-layer-event="info">
      <!-- Attribute renderers (see below) -->
      <div data-store-render data-store-devices></div>
      <div data-store-render data-store-subscription data-store-subscription-type="years"></div>
      <div data-store-render data-store-price="discounted || full"></div>
      <a data-store-render data-store-buy-link>Buy</a>
      <!-- Eta template (text) -->
      <p>Now at only {{= it.option.price.discounted }}!</p>
      <!-- Eta template (attribute, implicit) -->
      <div title="Devices {{= it.option.devices }}"></div>
      <!-- Hide via DSL using merged context -->
      <div data-store-render data-store-hide="!it.option.price.discounted">
        Hidden when discounted price doesn't exists
      </div>
      <!-- Actions -->
      <button data-store-action data-store-set-devices="25">25 devices</button>
    </bd-option>
  </bd-product>
</bd-root>

Store Config

  • trialLinks and overrides come from @repobit/dex-store and work transparently with these elements. You pass them when creating the Store and render them via attributes like data-store-trial-link or by relying on overridden option fields.

Example advanced config when constructing the store:

import { Store } from '@repobit/dex-store';

const store = new Store({
  locale  : 'en-us',
  provider: { name: 'vlaicu' },

  // Map productId -> campaign -> optionVariation -> trial URL
  // productId is the final id after adaptor mapping (e.g. 'com.bitdefender.tsmd.v2').
  // optionVariation key format: '<devices>-<subscription>' (e.g. '5-12').
  trialLinks: {
    'com.bitdefender.tsmd.v2': {
      default: {
        '5-12' : 'https://trial.example.com/default/5-12',
        '10-12': 'https://trial.example.com/default/10-12'
      },
      PromoX: {
        '5-12' : 'https://trial.example.com/promox/5-12',
        '10-12': 'https://trial.example.com/promox/10-12'
      }
    }
  },

  // Per-product overrides for campaign and/or options
  // - Set/redirect campaign via `default.campaign` or `[campaign].campaign`
  // - Merge option fields per variation; use `null` to remove an option
  overrides: {
    'com.bitdefender.tsmd.v2': {
      // Applies when no explicit campaign is requested
      default: {
        campaign: 'OvDefault'
      },
      PromoX: {
        campaign: 'PromoX',
        options : {
          '5-12' : { discountedPrice: 49.99, buyLink: 'https://example.com/override/buy' },
          '10-12': null // delete this variation
        }
      }
    }
  }
});

Notes

  • data-store-trial-link uses trialLinks to set the anchor href. If no mapping exists, the attribute is left untouched.
  • overrides.options merges into each option; you can update buyLink, discountedPrice, etc., or delete an entire variation with null.
  • Keys are resolved against the product id returned by the provider (after adaptor mapping). If you don’t use mappings, it’s the id you pass in <bd-product product-id="...">.

Rendering model

  • Add data-store-render to any element you want updated by the pipeline.
  • A single binder subscribes to option, product and aggregated state contexts and renders attributes + Eta templates.
  • Scoping is natural: a node sees the nearest provider up the tree (e.g., it.option.* is only available inside <bd-option>).

Supported attributes

  • data-store-devices

    • Renders option devices to text nodes, <input> value, or <select> options (adds data-store-set-devices on each option)
    • Optional label helpers: data-store-text-single="device", data-store-text-many="devices"
  • data-store-subscription

    • Renders option subscription similarly; add data-store-subscription-type="years|months"
    • Label helpers: data-store-text-single, data-store-text-many
  • data-store-price

    • Allowed tokens: full, discounted, full-monthly, discounted-monthly
    • Supports OR semantics via || to choose the first available variant:
      • data-store-price="discounted || full"
  • data-store-discount

    • Allowed tokens: value, percentage, value-monthly, percentage-monthly
    • Supports || fallbacks
  • Aggregated state (min/max across options):

    • data-store-context-price tokens: min-full, max-full, min-full-monthly, max-full-monthly, min-discounted, max-discounted, min-discounted-monthly, max-discounted-monthly
    • data-store-context-discount tokens: min-value, max-value, min-value-monthly, max-value-monthly, min-percentage, max-percentage, min-percentage-monthly, max-percentage-monthly
  • Links

    • data-store-buy-link sets anchor href and useful data-* attributes
    • data-store-trial-link sets anchor href to the trial link (if configured in @repobit/dex-store store config)
  • Hide DSL

    • data-store-hide="<boolean expression>" with an optional data-store-hide-type="display|opacity|visibility"
    • Expression is compiled and evaluated against the unified context:
      • it.option.* current option data. Price- and discount-related fields are formatted strings (currency-aware). Do not rely on numeric math for prices; they vary by currency. Devices/subscription remain numeric.
      • it.product.* id/campaign/name
      • it.state.* aggregated min/max data (also available under it.ctx)
      • any keys returned from your root.derived
    • Examples:
      • data-store-hide="!it.option.price.discounted" (hide when no discounted price)
      • data-store-hide="it.product.campaign === 'test'"

Eta templates

  • Text/HTML: any element that is not a provider and doesn’t contain nested providers is treated as a whole-template; innerHTML is compiled once and morphed via nanomorph. This preserves existing DOM event listeners and state.
  • Attributes:
    • Implicit: any attribute whose value contains {{ is rendered via Eta
  • The Eta context variable is it (Eta default). It contains:
    • it.option.* (inside <bd-option>)
    • it.product.* (inside <bd-product>)
    • it.state.* and it.ctx.* (inside any provider subtree)
    • your derived overlay merged at top-level (see below)

Derived variables/functions

  • Provide a function at the root: root.derived = async ({ option, product, state }) => ({ ... })
  • The returned object is merged into the Eta/DSL context:
    • Example: ({ mails: (p) => (option?.getDevices?.()/p)*100, option: { someVar: state.discount.value.min } })
    • Use it in Eta: {{= it.mails(10) }} or {{= it.option.someVar }}
    • Use it in hide: data-store-hide="it.mails(10) >= 50"

DSL context reference

The DSL and Eta contexts use Eta’s default variable name it. The following keys are available:

  • it.option

    • price
      • full: formatted full price (string)
      • discounted: formatted discounted price (string)
      • fullMonthly: formatted monthly full price (string)
      • discountedMonthly: formatted monthly discounted price (string)
    • discount
      • value: formatted discount amount (string)
      • percentage: formatted percentage discount with symbol (string)
      • valueMonthly: formatted monthly discount amount (string)
      • percentageMonthly: formatted monthly percentage with symbol (string)
    • links
      • buy: buy URL (string)
      • trial: trial URL if available (string)
    • devices: number
    • subscription: number
  • it.product

    • id: string
    • campaign: string
    • name: string
  • it.state (also available as it.ctx)

    • price
      • full
        • min: formatted string
        • max: formatted string
        • monthly
          • min: formatted string
          • max: formatted string
      • discounted
        • min: formatted string
        • max: formatted string
        • monthly
          • min: formatted string
          • max: formatted string
    • discount
      • percentage
        • min: formatted string
        • max: formatted string
        • monthly
          • min: formatted string
          • max: formatted string
      • value
        • min: formatted string
        • max: formatted string
        • monthly
          • min: formatted string
          • max: formatted string

Notes

  • All price/discount values in the DSL are formatted strings (currency-aware). Do not perform numeric comparisons on them. Prefer truthiness checks (e.g., !it.option.price.discounted).
  • it.ctx is an alias of it.state for convenience.

Data Layer

  • Provide a function on <bd-root>: root.dataLayer = ({ option, event }) => { ... }.
    • Called once per <bd-option> instance that declares data-layer-event (on first successful load).
    • Safe if attached after the option loads; it still fires once when available.
    • Intended for analytics (e.g., pushing to window.dataLayer).
  • Event name is set on each <bd-option> with data-layer-event.
    • Canonical values: all, info, comparison. Any custom string is also accepted.
  • Payload shape passed to your callback:
    • event: string event name.
    • option: ProductOption from @repobit/dex-store with getters like getProduct(), getVariation(), getDevices(), getSubscription(), getBuyLink(), getDiscountedPrice().

Example:

<script type="module">
  import { registerContextNodes, registerActionNodes } from '@repobit/dex-store-elements';
  import { Store } from '@repobit/dex-store';

  window.addEventListener('DOMContentLoaded', async () => {
    registerContextNodes();
    const root = document.querySelector('bd-root');
    root.store = new Store({ locale: 'en-us', provider: { name: 'vlaicu' } });

    root.dataLayer = ({ option, event }) => {
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event,
        productId: option.getProduct().getId(),
        campaign : option.getProduct().getCampaign(),
        variation: option.getVariation(),
        devices  : option.getDevices(),
        subscription: option.getSubscription()
      });
    };

    registerActionNodes(root);
  });
</script>

<bd-root store-name="root">
  <bd-product store-name="product" product-id="com.bitdefender.tsmd.v2">
    <bd-option devices="5" subscription="12" data-layer-event="info">
      <!-- your UI here -->
    </bd-option>
  </bd-product>
</bd-root>

Behavior notes

  • Fires only once per <bd-option> instance (first load). Subsequent changes via actions or deltas do not trigger the callback again.
  • Set different data-layer-event values on different options if you need multiple distinct analytics events.

Actions

  • Add data-store-action to elements to emit store events.
    • Set absolute values: data-store-set-devices, data-store-set-subscription, data-store-set-id, data-store-set-campaign
    • Update by delta/sequence: data-store-set-type="devices|subscription", data-store-set-delta="next|prev|<number>"
    • Source identifier: add data-store-id="someId" to tag the event's storeId. Providers with ignore-events that include someId will drop these events.
  • Initialize once per mount with:
    • import { registerActionNodes } from '@repobit/dex-store-elements'
    • registerActionNodes(root)

Registration and initialization

  • Element registration:
    • import { registerContextNodes } from '@repobit/dex-store-elements'; registerContextNodes();
  • Rendering: import { registerRenderNodes } from '@repobit/dex-store-elements'
  • Actions: import { registerActionNodes } from '@repobit/dex-store-elements'

Note: The package is side-effect free; elements are registered only when registerContextNodes() is called.

State & event controls

These attributes are available on <bd-state> and any element that extends it (<bd-root>, <bd-product>, <bd-option>).

  • ignore-events="store-a, store-b"
    • Comma-separated list of action ids. Events whose source element has a matching data-store-id are ignored by this node (and its subtree). Works for both action and delta events.
    • Coupling with data-store-id (on action elements):
      • Example:
        • <button data-store-action data-store-id="devicesBtn" data-store-set-devices="25"> dispatches an event with storeId: "devicesBtn".
        • <bd-option ignore-events="devicesBtn"> ignores that event while still accepting events from other buttons.
      • You can list multiple ids: ignore-events="devicesBtn, subscriptionBtn".
  • ignore-events-parent
    • Ignores events received from ancestor providers and only reacts to DOM-bubbled events that originate within the current subtree.
    • <bd-context> is a convenience element that defaults this behavior to enabled. It’s equivalent to <bd-state ignore-events-parent>.
    • Example:
      <bd-state store-name="outer">
        <!-- Global action updates state here -->
        <button data-store-action data-store-set-devices="25"></button>
      
        <!-- Isolated island: only inner actions are observed -->
        <bd-context store-name="island">
          <button data-store-action data-store-set-devices="5"></button>
        </bd-context>
      </bd-state>
      In this setup, the inner <bd-context> ignores the outer button’s events.
  • no-collect
    • Turns off automatic option collection for aggregated state. Set this when you want the node to work locally without contributing to shared min/max computations (e.g., for preview widgets).

Caveats

  • Scoping: attribute Eta and hide can only see contexts provided by ancestors. For example, it.option.* is only available inside <bd-option>.
  • Nested providers: inner providers render their own subtrees; outer nodes can still render attributes safely even when they contain nested providers.

TypeScript

  • Custom element classes are exported from a dedicated entry for advanced use:
    • import { RootNode, ProductNode, OptionNode, StateNode } from '@repobit/dex-store-elements'
  • The derived signature:
    import type { derivedContextType } from '@repobit/dex-store-elements/src/contexts/context.derived';
    const derived: derivedContextType = async ({ option, product, state, store }) => ({ ... });

License

ISC