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

@searchstax-inc/searchstudio-ux-angular

v4.1.72

Published

Library to build searchstudio search page

Readme

searchstudio-ux-angular

Library to build searchstudio search page

changelog

changelog is documented at CHANGELOG.md

Installation

npm install following package npm install --save @searchstax-inc/searchstudio-ux-angular

Usage

After importing SearchstaxWrapper component needs to wrap all other components.

Make sure your module imports SearchstudioUxAngularModule:

imports: [ SearchstudioUxAngularModule ],

Initialization

Initialization object needs to be of type: ISearchstaxConfig

Initialization example

Add following code to your ts file and fill config

 config = {
    searchURL: '',
    suggesterURL: '',
    trackApiKey: '',
    searchAuth: '',
    authType: 'basic',
    relatedSearchesAPIKey: '',
    relatedSearchesURL: '',
    analyticsBaseUrl: 'https://analytics-us-east.searchstax.com'
  }

  beforeSearch(props: ISearchObject) {
    // gets searchProps, if passed along further search will execute, if null then event gets canceled
    // props can be modified and passed along
    const propsCopy = { ...props }
    // propsCopy.term = propsCopy.term;
    return propsCopy
  }
  afterSearch(results: ISearchstaxParsedResult[]) {
    const copy = [...results]
    // copy.splice(0, 1);
    return copy
  }

  afterAutosuggest(result: ISearchstaxSuggestResponse) {
    const copy = { ...result }
    return copy
  }

  beforeAutosuggest(props: ISearchstaxSuggestProps) {
    // gets suggestProps, if passed along further autosuggest will execute, if null then event gets canceled
    // props can be modified and passed along
    const propsCopy = { ...props }
    // propsCopy.term = propsCopy.term + '222';
    return propsCopy
  }

  afterLinkClick(results: ISearchstaxParsedResult): ISearchstaxParsedResult {
    // gets result that was clicked, if passed along further functions will execute, if null then event gets canceled
    const copy = { ...results }

    return copy
  }

  initialized(searchstax: Searchstax) {
    console.log('searchstax', searchstax);
  }

Initial layout

Our base theme is designed with this layout in mind but it is optional as all widgets have id parameters and can be attached to any element.

<div>
  <app-searchstax-wrapper
    [searchURL]="config.searchURL"
    [suggesterURL]="config.suggesterURL"
    [trackApiKey]="config.trackApiKey"
    [searchAuth]="config.searchAuth"
    [beforeSearch]="beforeSearch"
    [afterSearch]="afterSearch"
    (initialized)="initialized($event)"
  >
    <div class="searchstax-page-layout-container">
      <app-searchstax-input
        [afterAutosuggest]="afterAutosuggest"
        [beforeAutosuggest]="beforeAutosuggest"
        [suggestAfterMinChars]="3"
      ></app-searchstax-input>
      <div class="search-details-container">
        <app-searchstax-search-feedback></app-searchstax-search-feedback>
        <app-searchstax-search-sorting></app-searchstax-search-sorting>
      </div>
      <div class="searchstax-page-layout-facet-result-container">
        <div class="searchstax-page-layout-facet-container">
          <app-searchstax-search-facets
            [facetingType]="'showUnavailable'"
            [itemsPerPageDesktop]="2"
            [itemsPerPageMobile]="99"
          ></app-searchstax-search-facets>
        </div>
        <div class="searchstax-page-layout-result-container">
          <app-searchstax-external-promotions></app-searchstax-external-promotions>
          <app-searchstax-results
            [afterLinkClick]="afterLinkClick"
          ></app-searchstax-results>
          <app-searchstax-related-searches
            [relatedSearchesURL]="config.relatedSearchesURL"
            [relatedSearchesAPIKey]="config.relatedSearchesAPIKey"
          ></app-searchstax-related-searches>

          <app-searchstax-search-pagination></app-searchstax-search-pagination>
        </div>
      </div>
    </div>
  </app-searchstax-wrapper>
</div>

widgets

Following widgets are available:

Answer Widget

Input Widget

Location Widget

Result Widget

Facets Widget

Pagination Widget

SearchFeedback Widget

RelatedSearches Widget

ExternalPromotions Widget

sorting Widget

Answer Widget

SearchStax Site Search solution offers Angular widgets to assist in building your custom search page.

The AppSearchstaxAnswer component for Angular provides an AI answer widget for your searches.

Usage

<app-searchstax-answer [showMoreAfterWordCount]="100"></app-searchstax-answer>

Props

  • showMoreAfterWordCount - number(default 100) determining after how many words UI will show “Show More” view.
  • feedbackWidget – an optional object that configures thumbs-up and thumbs-down feedback functionality.

Example of feedbackWidget config:

const feedbackConfig = {
    renderFeedbackWidget: true,
    emailOverride: searchstaxEmailOverride,
    thumbsUpValue: 10,
    thumbsDownValue: 0
  }

searchAnswerTemplate

The templates prop allows customizing the answer UI.

It receives the following props:

  • answerData – Data object of type: ISearchstaxAnswerData
  • showMore - Handler for exiting the show more view

Example

<app-searchstax-answer [showMoreAfterWordCount]="100" [templateOverride]="answerTemplate" [feedbackwidget]="feedbackConfig">
<ng-template
      #answerTemplate
      let-answerData="answerData"
      let-showMore="showMore"
      let-context="context"
    >
      <div class="searchstax-answer-wrap">
        <div class="searchstax-answer-icon"></div>
        <div>
          <div
            [class]="{
              'searchstax-answer-container': true,
              'searchstax-answer-show-more': answerData.showMoreButtonVisible
            }"
          >
            <div class="searchstax-answer-title">Smart Answers</div>
            <div
              class="searchstax-answer-error"
              *ngIf="answerData.shouldShowAnswerError"
              [innerHTML]="answerData.answerErrorMessage"
            ></div>
            <div
              class="searchstax-answer-description"
              [innerHTML]="answerData.fullAnswerFormatted"
            ></div>
            <div
              *ngIf="answerData.answerLoading"
              class="searchstax-answer-loading"
            ></div>
          </div>
          <div
            *ngIf="answerData.showMoreButtonVisible"
            class="searchstax-answer-load-more-button-container"
          >
            <button
              class="searchstax-answer-load-more-button"
              (click)="showMore($event, context)"
            >
              Show More
            </button>
          </div>
        </div>
        <div class="searchstax-answer-footer">
          <div id="feedbackWidgetContainer"></div>
          <div class="searchstax-lightweight-widget-separator-inline"></div>
          <p class="searchstax-disclaimer">Generative AI is Experimental</p>
        </div>
      </div>
    </ng-template>
      </app-searchstax-answer>

Input Widget

The SearchStax Site Search solution offers an Angular input widget to assist with your custom search page.

The SearchstaxInputWidget component for Angular provides a search input with autosuggest/autocomplete functionality. Usage

<app-searchstax-input
        [afterAutosuggest]="afterAutosuggest"
        [beforeAutosuggest]="beforeAutosuggest"
        [suggestAfterMinChars]="3"
      ></app-searchstax-input>

Props

  • suggestAfterMinChars - default 3. Number of characters needed for autosuggest to start triggering
  • beforeAutosuggest - callback function that gets called before firing autosuggest. autosuggestProps are being passed as a property and can be modified, if passed along further search will execute with modified properties, if null is returned then event gets canceled and search never fires.
  • afterAutosuggest - callback function that gets called after autosuggest has values but before rendering. It needs to return same type of data but it can be modified.
  • templateOverride - template override. look at examples below

inputWidgetTemplate

The templateOverride prop allows customizing the input UI.

It receives the following props:

  • suggestions – Array of autosuggestion results
  • onMouseLeave - Handler for mouse leave event
  • onMouseOver - Handler for mouse over event
  • onMouseClick - Handler for mouse click event

example

<app-searchstax-input
        [afterAutosuggest]="afterAutosuggest"
        [beforeAutosuggest]="beforeAutosuggest"
        [suggestAfterMinChars]="3"
        [templateOverride]="inputTemplate"
      >
     <ng-template
      #inputTemplate
      let-suggestions="suggestions"
      let-onMouseLeave="onMouseLeave"
      let-onMouseOver="onMouseOver"
      let-onMouseClick="onMouseClick"
      let-context="context"
      let-locationWidgetConfig="locationWidgetConfig"
    >
      <div class="searchstax-search-input-wrapper">
        <input
          type="text"
          id="searchstax-search-input"
          class="searchstax-search-input"
          placeholder="SEARCH FOR..."
          aria-label="search"
        />
        <div
          class="searchstax-autosuggest-container"
          [ngClass]="{ hidden: suggestions.length === 0 }"
          (mouseleave)="onMouseLeave(context)"
        >
          <div
            class="searchstax-autosuggest-item"
            *ngFor="let suggestion of suggestions"
          >
            <div
              class="searchstax-autosuggest-item-term-container"
              [innerHtml]="suggestion.term"
              (mouseover)="onMouseOver(suggestion, context)"
              tabindex="0"
              (click)="onMouseClick($event, context)"
            ></div>
          </div>
        </div>
      </div>
      <app-searchstax-search-location
        [locationDecode]="locationWidgetConfig.locationDecode"
        [locationDecodeCoordinatesToAddress]="
          locationWidgetConfig.locationDecodeCoordinatesToAddress
        "
        [locationSearchEnabled]="locationWidgetConfig.locationSearchEnabled"
        [locationValuesOverride]="locationWidgetConfig.locationValuesOverride"
      >
      </app-searchstax-search-location>
      <button
        class="searchstax-spinner-icon"
        id="searchstax-search-input-action-button"
        role="button"
        aria-label="search"
      ></button>
    </ng-template>
      </app-searchstax-input>

Location Widget

SearchStax Site Search solution offers a Angular search-location widget to assist with your custom search page.

The SearchstaxLocationWidget provides a location search input with location-based search functionality.

Usage

const config = {
  appId: "APP_ID",
  relatedSearchesAPIKey: "KEY"
}

const locationWidgetConfig = {
    locationDecode: (term: string): Promise<ISearchstaxLocation> => {
      return new Promise((resolve) => {
        // make a request to google geocoding API to retrieve lat, lon and address

        const geocodingAPIKey = "GEOCODING_API_KEY";
        const geocodingURL = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(
          term
        )}&key=${geocodingAPIKey}`;
        fetch(geocodingURL)
          .then((response) => response.json())
          .then((data) => {
            if (data.status === "OK" && data.results.length > 0) {
              const result = data.results[0];
              const location = {
                lat: result.geometry.location.lat,
                lon: result.geometry.location.lng,
                address: result.formatted_address,
              };
              resolve(location);
            } else {
              resolve({
                address: undefined,
                lat: undefined,
                lon: undefined,
                error: true,
              });
            }
          })
          .catch(() => {
            resolve({
              address: undefined,
              lat: undefined,
              lon: undefined,
              error: true,
            });
          });
      });
    },
    locationDecodeCoordinatesToAddress: (lat: string, lon: string): Promise<ISearchstaxLocation> => {
      return new Promise((resolve) => {
        fetch(
          `https://geocoding-staging.searchstax.co/reverse?location=${lat},${lon}&components=country:US&app_id=${config.appId}`,
          {
            method: "GET",
            headers: {
              Authorization: `Token ${config.relatedSearchesAPIKey}`,
            },
          }
        )
          .then((response) => response.json())
          .then((data) => {
            if (data.status === "OK" && data.results.length > 0) {
              const result = data.results[0];
              resolve({
                address: result.formatted_address,
                lat: lat,
                lon: lon,
                error: false,
              });
            } else {
              resolve({
                address: undefined,
                lat: lat,
                lon: lon,
                error: true,
              });
            }
          })
          .catch(() => {
            resolve({
              address: undefined,
              lat: lat,
              lon: lon,
              error: true,
            });
          });
      });
    },
    locationSearchEnabled: false,
    locationValuesOverride: {
      locationDistanceEnabled: true,
      filterValues: ["any", "1000"],
      filterUnit: "miles",
      locationFilterDefaultValue: "any"
    },
  }
 <app-searchstax-search-location
            [locationDecode]="locationWidgetConfig.locationDecodeFunction"
            [locationDecodeCoordinatesToAddress]="locationWidgetConfig.locationDecodeCoordinatesToAddress"
            [locationValuesOverride]="locationWidgetConfig.locationValuesOverride"
            [locationSearchEnabled]="locationWidgetConfig.locationSearchEnabled"
          >
 </app-searchstax-search-location>

Props

  • locationDecode - callback function to override location decoding
  • locationDecodeCoordinatesToAddress - callback function to override location decoding
  • locationValuesOverride - location widget values
  • locationSearchEnabled - boolean stating if locationSearch is locationSearchEnabled
  • templateOverride - template override. look at examples below

Template Override

The searchLocationTemplate prop allows customizing the location input UI.

It receives the following props:

  • locationData – data containing info on when to show certain elements
  • inputValue – value of location input
  • locationBlur - Handler for location input blur
  • radiusChange - Handler for location radius change
  • selectValue - Value of radius select
  • inputChange - location input change handler
  • locationError - boolean stating if location has error
  • getCurrentLocation - Handler for getting current location from browser

Example

const config = {
  appId: "APP_ID",
  relatedSearchesAPIKey: "KEY"
}

const locationWidgetConfig = {
    locationDecode: (term: string): Promise<ISearchstaxLocation> => {
      return new Promise((resolve) => {
        // make a request to google geocoding API to retrieve lat, lon and address

        const geocodingAPIKey = "GEOCODING_API_KEY";
        const geocodingURL = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(
          term
        )}&key=${geocodingAPIKey}`;
        fetch(geocodingURL)
          .then((response) => response.json())
          .then((data) => {
            if (data.status === "OK" && data.results.length > 0) {
              const result = data.results[0];
              const location = {
                lat: result.geometry.location.lat,
                lon: result.geometry.location.lng,
                address: result.formatted_address,
              };
              resolve(location);
            } else {
              resolve({
                address: undefined,
                lat: undefined,
                lon: undefined,
                error: true,
              });
            }
          })
          .catch(() => {
            resolve({
              address: undefined,
              lat: undefined,
              lon: undefined,
              error: true,
            });
          });
      });
    },
    locationDecodeCoordinatesToAddress: (lat: string, lon: string): Promise<ISearchstaxLocation> => {
      return new Promise((resolve) => {
        fetch(
          `https://geocoding-staging.searchstax.co/reverse?location=${lat},${lon}&components=country:US&app_id=${config.appId}`,
          {
            method: "GET",
            headers: {
              Authorization: `Token ${config.relatedSearchesAPIKey}`,
            },
          }
        )
          .then((response) => response.json())
          .then((data) => {
            if (data.status === "OK" && data.results.length > 0) {
              const result = data.results[0];
              resolve({
                address: result.formatted_address,
                lat: lat,
                lon: lon,
                error: false,
              });
            } else {
              resolve({
                address: undefined,
                lat: lat,
                lon: lon,
                error: true,
              });
            }
          })
          .catch(() => {
            resolve({
              address: undefined,
              lat: lat,
              lon: lon,
              error: true,
            });
          });
      });
    },
    locationSearchEnabled: false,
    locationValuesOverride: {
      locationDistanceEnabled: true,
      filterValues: ["any", "1000"],
      filterUnit: "miles",
      locationFilterDefaultValue: "any"
    },
  }
<app-searchstax-search-location
            [locationDecode]="locationWidgetConfig.locationDecode"
            [templateOverride]="locationTemplate"
            [locationValuesOverride]="locationWidgetConfig.locationValuesOverride"
            [locationSearchEnabled]="locationWidgetConfig.locationSearchEnabled"
            [locationDecodeCoordinatesToAddress]="locationWidgetConfig.locationDecodeCoordinatesToAddress"
            [templateOverride]="locationTemplate"
          >
              <ng-template
      #locationTemplate
      let-context="context"
      let-locationData="locationData"
      let-inputValue="inputValue"
      let-selectValue="selectValue"
      let-radiusChange="radiusChange"
      let-inputChange="inputChange"
      let-locationBlur="locationBlur"
      let-locationError="locationError"
      let-getCurrentLocation="getCurrentLocation"
    >
      <div
        class="searchstax-location-input-container"
        data-test-id="searchstax-location-input-container"
        *ngIf="locationData"
      >
        <div class="searchstax-location-input-wrapper">
          <span class="searchstax-location-input-label">NEAR</span>
          <div class="searchstax-location-input-wrapper-inner">
            <input
              type="text"
              id="searchstax-location-input"
              class="searchstax-location-input"
              [ngClass]="{
                'searchstax-input-location-error': locationError
              }"
              placeholder="Zip, Postal Code or City..."
              aria-label="Search Location Input"
              data-test-id="searchstax-location-input"
              [value]="inputValue"
              (change)="inputChange($event, context)"
              (blur)="locationBlur(context)"
            />
            <button
              (click)="getCurrentLocation(context)"
              class="searchstax-get-current-location-button"
            >
              Use my current location
            </button>
          </div>
          <span
            *ngIf="locationData.shouldShowLocationDistanceDropdown"
            class="searchstax-location-input-label"
            >WITHIN</span
          >
          <select
            *ngIf="locationData.shouldShowLocationDistanceDropdown"
            id="searchstax-location-radius-select"
            class="searchstax-location-radius-select"
            aria-label="Search Location Radius Select"
            data-test-id="searchstax-location-radius-select"
            (change)="radiusChange($event, context)"
            [(ngModel)]="context.selectValue"
          >
            <option
              *ngFor="let value of locationData.locationSearchDistanceValues"
              [value]="value.value"
            >
              {{ value.label }}
            </option>
          </select>
        </div>
      </div>
    </ng-template>

          </app-searchstax-search-location>

Result Widget

The SearchStax Site Search solution offers an Angular results widget to assist with your custom search page.

The SearchstaxResultsWidget for Angular component displays the search results.

Usage

<app-searchstax-results
            [afterLinkClick]="afterLinkClick"
            [renderMethod]="'pagination'"
            [resultsPerPage]="10"
          ></app-searchstax-results>

Props

  • renderMethod – either “pagination” or “infiniteScroll”.
  • resultsPerPage – number of results on a page.
  • afterLinkClick – Callback function invoked when a result link is clicked. Allows modifying the result object.
  • noResultTemplate - template override for no results view
  • resultsTemplate - template override for result item

Result Template Override

The resultsTemplate prop allows customizing the result UI.

It receives following props:

  • searchResults - Array of result items
  • resultClicked - Handler for result click events

No Results Template Override

The noResultTemplate prop allows customizing the result UI.

It receives following props:

  • searchResults - Array of result items
  • resultClicked - Handler for result click events
  • searchTerm - Search input term
  • metadata - Metadata object
  • executeSearch - Handler for executing srearch
  • store - Main store of app

Example of default render method

<app-searchstax-results
            [afterLinkClick]="afterLinkClick"
            [resultsTemplate]="resultsTemplate"
            [noResultsTemplate]="noResultsTemplate"
          >
     <ng-template
      #resultsTemplate
      let-searchResults="searchResults"
      let-resultClicked="resultClicked"
      let-isImage="isImage"
      let-context="context"
    >
      <div class="searchstax-search-results">
        <a
          *ngFor="let searchResult of searchResults"
          [href]="searchResult.url"
          [attr.data-searchstax-unique-result-id]="searchResult.uniqueId"
          (click)="resultClicked(searchResult, $event, context)"
          (keyup.enter)="resultClicked(searchResult, $event, context)"
          (keyup.space)="resultClicked(searchResult, $event, context)"
          [attr.aria-labelledby]="'title-' + searchResult.uniqueId"
          tabindex="0"
          class="searchstax-result-item-link searchstax-result-item-link-wrapping"
        >
          <div
            class="searchstax-search-result"
            [ngClass]="{ 'has-thumbnail': searchResult.thumbnail }"
          >
            <div
              *ngIf="searchResult.promoted"
              class="searchstax-search-result-promoted"
            ></div>
            <div
              *ngIf="searchResult.ribbon"
              [innerHTML]="searchResult.ribbon"
              class="searchstax-search-result-ribbon"
            ></div>
            <img
              alt=""
              *ngIf="searchResult.thumbnail"
              [src]="searchResult.thumbnail"
              class="searchstax-thumbnail"
            />
            <div class="searchstax-search-result-title-container">
              <span
                class="searchstax-search-result-title"
                [id]="'title-' + searchResult.uniqueId"
                [innerHTML]="searchResult.title"
              ></span>
            </div>
            <p
              tabindex="0"
              *ngIf="searchResult.paths"
              class="searchstax-search-result-common"
              [innerHTML]="searchResult.paths"
            ></p>
            <p
              tabindex="0"
              *ngIf="searchResult.description"
              [innerHTML]="searchResult.description"
              class="searchstax-search-result-description searchstax-search-result-common"
            ></p>
            <div *ngFor="let unmappedField of searchResult.unmappedFields">
              <div
                *ngIf="isImage(unmappedField)"
                class="searchstax-search-result-image-container"
              >
                <img
                  alt=""
                  [src]="unmappedField.value"
                  class="searchstax-result-image"
                />
              </div>
              <div *ngIf="!isImage(unmappedField)">
                <p
                  tabindex="0"
                  class="searchstax-search-result-common"
                  [innerHTML]="unmappedField.value"
                ></p>
              </div>
            </div>
          </div>
        </a>
      </div>
    </ng-template>
     <ng-template
      #noResultsTemplate
      let-metadata="metadata"
      let-searchTerm="searchTerm"
      let-executeSearch="executeSearch"
      let-context="context"
    >
      <div class="searchstax-no-results">
        Showing <strong>no results</strong> for
        <strong>"{{ searchTerm }}"</strong>
        <br />
        <span *ngIf="metadata?.spellingSuggestion"
          >&nbsp;Did you mean
          <a
            href="#"
            [attr.aria-label]="'did you mean: ' + metadata?.spellingSuggestion"
            class="searchstax-suggestion-term"
            (click)="
              executeSearch(metadata?.spellingSuggestion ?? '', $event, context)
            "
            >{{ metadata?.spellingSuggestion }}</a
          >?</span
        >
      </div>
      <ul>
        <li>
          Try searching for search related terms or topics. We offer a wide
          variety of content to help you get the information you need.
        </li>
      </ul>
    </ng-template>

          </app-searchstax-results>

Example of infinite scroll and pagination render methods

<app-searchstax-results
            [afterLinkClick]="afterLinkClick"
            [renderMethod]="'infiniteScroll'"
          ></app-searchstax-results>

Pagination Widget

The SearchStax Site Search solution offers an Angular pagination widget to assist with you custom search page.

The SearchstaxPaginationWidget for Angular displays pagination controls for search results.

Usage

<app-searchstax-search-pagination></app-searchstax-search-pagination>

Props

  • infiniteScrollTemplateOverride - template override for infinite scroll view pagination
  • templateOverride - template override for standard view pagination

Main Template Override

Main template for the pagination controls.

It receives following props:

  • paginationData – Pagination info object
  • previousPage – Handler for previous page click
  • nextPage - – Handler for next page click

Infinite Scroll Template Override

Main template for the pagination controls in infinite scroll mode.

It receives following props:

  • isLastPage - boolean, true if its last page
  • results - results.length can be used if there are results

Example

<app-searchstax-search-pagination [templateOverride]="paginationTemplate">
     <ng-template
      #paginationTemplate
      let-paginationData="paginationData"
      let-previousPage="previousPage"
      let-nextPage="nextPage"
      let-context="context"
    >
      <div class="searchstax-pagination-container">
        <div class="searchstax-pagination-content">
          <a
            class="searchstax-pagination-previous"
            [class]="paginationData.isFirstPage ? 'disabled' : ''"
            (click)="previousPage($event, context)"
            (keyup.enter)="previousPage($event, context)"
            (keyup.space)="previousPage($event, context)"
            tabindex="0"
            id="searchstax-pagination-previous"
          >
            &lt; Previous
          </a>
          <div class="searchstax-pagination-details">
            {{ paginationData?.startResultIndex }} -
            {{ paginationData?.endResultIndex }} of
            {{ paginationData?.totalResults }}
          </div>
          <a
            class="searchstax-pagination-next"
            [class]="paginationData.isLastPage ? 'disabled' : ''"
            (click)="nextPage($event, context)"
            (keyup.enter)="nextPage($event, context)"
            (keyup.space)="nextPage($event, context)"
            tabindex="0"
            id="searchstax-pagination-next"
            >Next ></a
          >
        </div>
      </div>
    </ng-template>
</app-searchstax-search-pagination>

// infinite scroll example

<app-searchstax-search-pagination [templateOverride]="paginationTemplate"
                                  [infiniteScrollTemplateOverride]="infiniteScrollTemplate">
                 <ng-template
      #infiniteScrollTemplate
      let-paginationData="paginationData"
      let-nextPage="nextPage"
      let-context="context"
    >
      <div class="searchstax-pagination-container">
        <div class="searchstax-pagination-content">
          <a
            class="searchstax-pagination-load-more"
            (click)="nextPage($event, context)"
            (keyup.enter)="nextPage($event, context)"
            (keyup.space)="nextPage($event, context)"
            tabindex="0"
          >
            Load More
          </a>
        </div>
      </div>
    </ng-template>
                 <ng-template
      #paginationTemplate
      let-paginationData="paginationData"
      let-previousPage="previousPage"
      let-nextPage="nextPage"
      let-context="context"
    >
      <div class="searchstax-pagination-container">
        <div class="searchstax-pagination-content">
          <a
            class="searchstax-pagination-previous"
            [class]="paginationData.isFirstPage ? 'disabled' : ''"
            (click)="previousPage($event, context)"
            (keyup.enter)="previousPage($event, context)"
            (keyup.space)="previousPage($event, context)"
            tabindex="0"
            id="searchstax-pagination-previous"
          >
            &lt; Previous
          </a>
          <div class="searchstax-pagination-details">
            {{ paginationData?.startResultIndex }} -
            {{ paginationData?.endResultIndex }} of
            {{ paginationData?.totalResults }}
          </div>
          <a
            class="searchstax-pagination-next"
            [class]="paginationData.isLastPage ? 'disabled' : ''"
            (click)="nextPage($event, context)"
            (keyup.enter)="nextPage($event, context)"
            (keyup.space)="nextPage($event, context)"
            tabindex="0"
            id="searchstax-pagination-next"
            >Next ></a
          >
        </div>
      </div>
    </ng-template>
          </app-searchstax-search-pagination>

Facets Widget

The SearchStax Site Search solution offers the SearchstaxFacetsWidget for Angular to display the search facets.

Facet Selection and Order

Facet lists are configured and ordered on the Site Search Faceting Tab.

Usage

<app-searchstax-search-facets
            [facetingType]="'showUnavailable'"
            [itemsPerPageDesktop]="2"
            [itemsPerPageMobile]="99"
          ></app-searchstax-search-facets>

Props

  • facetingType: "and" | "or" | "showUnavailable" | "tabs"; // type that determines how facets will behave
  • specificFacets?: string[]; // optional array of facet names that if provided will only render those facets
  • itemsPerPageDesktop: number; // default expanded facets for desktop
  • itemsPerPageMobile: number; // default expanded facets for mobile
  • templateOverrideMobile - template override for mobile view
  • templateOverrideDesktop - template override for desktop view
  • beforeFacetsRender (facets: IFacetData[]) => IFacetData[] — Called with the current facets array before the widget renders. Return the same or a modified array to filter, reorder, or transform facets (e.g., hide certain facets or change their order).

Main Template Desktop Override

Main wrapper template for desktop facets display.

It receives following props:

  • facetsTemplateDataDesktop - Facets data object
  • isNotDeactivated - Check if facet group is active
  • toggleFacetGroup - Toggle facet group active state
  • isChecked - Check if facet value is selected
  • selectFacet - Handler for facet select
  • showMoreLessDesktop - Show more/less facets handler
  • facetContainers - Object of facet DOM containers
  • updateRefDesktop - Handler for keeping checkbox references

Main Template Mobile Override

Main wrapper template for mobile facets display.

It receives following props:

  • facetsTemplateDataMobile - Facets data object
  • selectedFacetsCheckboxes - Selected facet values
  • isNotDeactivated - Check if facet group is active
  • toggleFacetGroup - Toggle facet group active state
  • isChecked - Check if facet value is selected
  • selectFacet - Handler for facet select
  • showMoreLessDesktop - Show more/less facets handler
  • facetContainers - Object of facet DOM containers
  • openOverlay - Handler to open mobile overlay
  • unselectFacet - Handler to unselect specific facet
  • unselectAll - Handler to unselect all facets
  • closeOverlay - Handler to close mobile overlay
  • updateRefMobile - Handler for keeping mobile checkbox references

Example

<app-searchstax-search-facets
            [facetingType]="'showUnavailable'"
            [itemsPerPageDesktop]="2"
            [itemsPerPageMobile]="99"
            [templateOverrideDesktop]="templateOverrideDesktop"
            [templateOverrideMobile]="templateOverrideMobile"
          >
                 <ng-template
      #templateOverrideDesktop
      let-facetsTemplateDataDesktop="facetsTemplateDataDesktop"
      let-isNotDeactivated="isNotDeactivated"
      let-context="context"
      let-toggleFacetGroup="toggleFacetGroup"
      let-facetValues="facetValues"
      let-isChecked="isChecked"
      let-selectFacet="selectFacet"
      let-showMoreLessDesktop="showMoreLessDesktop"
    >
      <div class="searchstax-facets-container-desktop">
        <div
          class="searchstax-facet-container"
          *ngFor="let facet of facetsTemplateDataDesktop?.facets"
          [class]="{ active: isNotDeactivated(facet.name, context) }"
        >
          <div>
            <div
              class="searchstax-facet-title-container"
              (click)="toggleFacetGroup(facet.name, context)"
            >
              <div class="searchstax-facet-title">
                {{ facet.label }}
              </div>
              <div class="searchstax-facet-title-arrow active"></div>
            </div>
            <div class="searchstax-facet-values-container" aria-live="polite">
              <div
                *ngFor="let facetValue of facetValues(facet); index as key"
                class="searchstax-facet-value-container"
                [class]="{
                  'searchstax-facet-value-disabled': facetValue.disabled
                }"
                #ref
              >
                <div class="searchstax-facet-input">
                  <input
                    type="checkbox"
                    class="searchstax-facet-input-checkbox"
                    [checked]="isChecked(facetValue, context)"
                    [attr.aria-label]="
                      facetValue.value + ' ' + facetValue.count
                    "
                    [disabled]="facetValue.disabled"
                    (click)="
                      selectFacet(ref, $event, facetValue, true, context)
                    "
                  />
                </div>
                <div
                  class="searchstax-facet-value-label"
                  (click)="selectFacet(ref, $event, facetValue, false, context)"
                >
                  {{ facetValue.value }}
                </div>
                <div
                  class="searchstax-facet-value-count"
                  (click)="selectFacet(ref, $event, facetValue, false, context)"
                >
                  ({{ facetValue.count }})
                </div>
              </div>
              <div
                class="searchstax-facet-show-more-container"
                *ngIf="facet.hasMoreFacets"
              >
                <div
                  class="searchstax-facet-show-more-container"
                  (click)="showMoreLessDesktop($event, facet, context)"
                  (keyup.enter)="showMoreLessDesktop($event, facet, context)"
                  (keyup.space)="showMoreLessDesktop($event, facet, context)"
                  tabindex="0"
                  role="button"
                >
                  <div
                    *ngIf="facet.showingAllFacets"
                    class="searchstax-facet-show-less-button searchstax-facet-show-button"
                  >
                    less
                  </div>
                  <div
                    *ngIf="!facet.showingAllFacets"
                    class="searchstax-facet-show-more-button searchstax-facet-show-button"
                  >
                    more
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </ng-template>
                 <ng-template
      #templateOverrideMobile
      let-isNotDeactivated="isNotDeactivated"
      let-context="context"
      let-toggleFacetGroup="toggleFacetGroup"
      let-facetValues="facetValues"
      let-isChecked="isChecked"
      let-selectFacet="selectFacet"
      let-openOverlay="openOverlay"
      let-selectedFacetsCheckboxes="selectedFacetsCheckboxes"
      let-unselectFacet="unselectFacet"
      let-unselectAll="unselectAll"
      let-facetMobileData="facetMobileData"
      let-facetsTemplateDataMobile="facetsTemplateDataMobile"
      let-closeOverlay="closeOverlay"
      let-showMoreLessDesktop="showMoreLessDesktop"
      let-showMoreLessMobile="showMoreLessMobile"
    >
      <div class="searchstax-facets-container-mobile">
        <div class="searchstax-facets-pills-container">
          <div
            class="searchstax-facets-pill searchstax-facets-pill-filter-by"
            (click)="openOverlay(context)"
          >
            <div class="searchstax-facets-pill-label">Filter By</div>
          </div>
          <div class="searchstax-facets-pills-selected">
            <div
              class="searchstax-facets-pill searchstax-facets-pill-facets"
              *ngFor="let facet of selectedFacetsCheckboxes"
              (click)="unselectFacet(facet, context)"
            >
              <div class="searchstax-facets-pill-label">
                {{ facet.value }} ({{ facet.count }})
              </div>
              <div class="searchstax-facets-pill-icon-close"></div>
            </div>
          </div>
          <div
            class="searchstax-facets-pill searchstax-clear-filters searchstax-facets-pill-clear-all"
            *ngIf="selectedFacetsCheckboxes.length"
            (click)="unselectAll(context)"
          >
            <div class="searchstax-facets-pill-label">Clear Filters</div>
          </div>
        </div>
        <div
          class="searchstax-facets-mobile-overlay"
          [class]="{
            'searchstax-show': facetMobileData(facetsTemplateDataMobile)
              ?.overlayOpened
          }"
        >
          <div class="searchstax-facets-mobile-overlay-header">
            <div class="searchstax-facets-mobile-overlay-header-title">
              Filter By
            </div>
            <div
              class="searchstax-search-close"
              (click)="closeOverlay(context)"
            ></div>
          </div>
          <div class="searchstax-facets-container-mobile">
            <div
              *ngFor="let facet of facetsTemplateDataMobile?.facets"
              class="searchstax-facet-container"
              [class]="{
                active: isNotDeactivated(facet.name, context)
              }"
            >
              <div>
                <div
                  class="searchstax-facet-title-container"
                  (click)="toggleFacetGroup(facet.name, context)"
                >
                  <div class="searchstax-facet-title">
                    {{ facet.label }}
                  </div>
                  <div class="searchstax-facet-title-arrow active"></div>
                </div>
                <div
                  class="searchstax-facet-values-container"
                  aria-live="polite"
                >
                  <div
                    *ngFor="let facetValue of facetValues(facet); index as key"
                    class="searchstax-facet-value-container"
                    [class]="{
                      'searchstax-facet-value-disabled': facetValue.disabled
                    }"
                    #ref
                  >
                    <div class="searchstax-facet-input">
                      <input
                        type="checkbox"
                        class="searchstax-facet-input-checkbox"
                        [checked]="isChecked(facetValue, context)"
                        [attr.aria-label]="
                          facetValue.value + ' ' + facetValue.count
                        "
                        [disabled]="facetValue.disabled"
                        (click)="
                          selectFacet(ref, $event, facetValue, true, context)
                        "
                      />
                    </div>
                    <div
                      class="searchstax-facet-value-label"
                      (click)="
                        selectFacet(ref, $event, facetValue, false, context)
                      "
                    >
                      {{ facetValue.value }}
                    </div>
                    <div
                      class="searchstax-facet-value-count"
                      (click)="
                        selectFacet(ref, $event, facetValue, false, context)
                      "
                    >
                      ({{ facetValue.count }})
                    </div>
                  </div>
                  <div
                    class="searchstax-facet-show-more-container"
                    *ngIf="facet.hasMoreFacets"
                  >
                    <div
                      class="searchstax-facet-show-more-container"
                      (click)="showMoreLessMobile($event, facet, context)"
                      (keyup.enter)="showMoreLessMobile($event, facet, context)"
                      (keyup.space)="showMoreLessMobile($event, facet, context)"
                      tabindex="0"
                    >
                      <div
                        *ngIf="facet.showingAllFacets"
                        class="searchstax-facet-show-less-button searchstax-facet-show-button"
                      >
                        less
                      </div>
                      <div
                        *ngIf="!facet.showingAllFacets"
                        class="searchstax-facet-show-more-button searchstax-facet-show-button"
                      >
                        more
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <button
            class="searchstax-facets-mobile-overlay-done"
            (click)="closeOverlay(context)"
          >
            Done
          </button>
        </div>
      </div>
    </ng-template>
          </app-searchstax-search-facets>

SearchFeedback Widget

The SearchStax Site Search solution includes an Angular feedback widget for your custom search page.

The SearchstaxFeedbackWidget for Angular displays search feedback and stats.

Usage

<app-searchstax-search-feedback></app-searchstax-search-feedback>

Props

  • templateOverride - template override for search feedback

Main Template Override

Main template for the search feedback message.

It receives following props:

  • searchFeedbackData - Feedback data object
  • onOriginalQueryClick - Handler for clicking on original query suggestion

Example

<app-searchstax-search-feedback [templateOverride]="feedbackTemplate">
                          <ng-template
      #feedbackTemplate
      let-searchFeedbackData="searchFeedbackData"
      let-onOriginalQueryClick="onOriginalQueryClick"
      let-context="context"
    >
      <h2 class="searchstax-feedback-container">
        Showing
        <b
          >{{ searchFeedbackData!.startResultIndex }} -
          {{ searchFeedbackData!.endResultIndex }}</b
        >
        of <b>{{ searchFeedbackData!.totalResults }}</b> results
        <span *ngIf="searchFeedbackData.searchTerm"
          >for "<b>{{ searchFeedbackData!.searchTerm }}</b
          >"
        </span>
        <div class="searchstax-feedback-container-suggested">
          <div *ngIf="searchFeedbackData.autoCorrectedQuery">
            Search instead for
            <a
              href="#"
              (click)="onOriginalQueryClick($event, context)"
              class="searchstax-feedback-original-query"
              [attr.aria-label]="
                'Search instead for: ' + searchFeedbackData!.originalQuery
              "
              >{{ searchFeedbackData!.originalQuery }}</a
            >
          </div>
        </div>
      </h2>
    </ng-template>
</app-searchstax-search-feedback>

RelatedSearches widget

The SearchStax Site Search solution offers an Angular related-searches widget for your custom search page.

The SearchstaxRelatedSearchesWidget displays related searches.

Usage

 <app-searchstax-related-searches
            [relatedSearchesURL]="config.relatedSearchesURL"
            [relatedSearchesAPIKey]="config.relatedSearchesAPIKey"
          ></app-searchstax-related-searches>

Props

  • relatedSearchesURL: API URL for fetching related searches
  • relatedSearchesAPIKey?: API key for related searches API
  • templateOverride - template override for related search

Main Template Override

Main template for related searches.

It receives following props:

  • relatedData - Related searches data object
  • executeSearch - Handler to run new search from related term

Example

<app-searchstax-related-searches
            [relatedSearchesURL]="config.relatedSearchesURL"
            [relatedSearchesAPIKey]="config.relatedSearchesAPIKey"
            [templateOverride]="relatedTemplate"
          >
                 <ng-template
      #relatedTemplate
      let-rand="rand"
      let-relatedData="relatedData"
      let-executeSearch="executeSearch"
      let-context="context"
    >
      <div
        class="searchstax-related-searches-container"
        [id]="'searchstax-related-searches-container' + rand"
      >
        Related searches: <span id="searchstax-related-searches"></span>
        <span
          class="searchstax-related-search"
          *ngIf="relatedData.relatedSearches"
        >
          <span
            *ngFor="let related of relatedData.relatedSearches"
            (click)="executeSearch(related, context)"
            (keyup.enter)="executeSearch(related, context)"
            (keyup.space)="executeSearch(related, context)"
            tabindex="0"
            class="searchstax-related-search searchstax-related-search-item"
            [attr.aria-label]="'Related search: ' + related.related_search"
          >
            {{ related.related_search }}<span *ngIf="!related.last">,</span>
          </span>
        </span>
      </div>
    </ng-template>
 </app-searchstax-related-searches>

ExternalPromotions widget

The SearchStax Site Search solution offers an Angular external-promotions widget for your custom search page.

The SearchstaxExternalPromotionsWidget displays external promotions fetched from the API.

Usage

<app-searchstax-external-promotions></app-searchstax-external-promotions>

Props

  • templateOverride - template override for external promotion

Main Template Override

Main template for external promotions.

It receives following props:

  • externalPromotionsData – External promotions data object
  • trackClick – Handler for tracking link clicks

Example

<app-searchstax-external-promotions [templateOverride]="externalPromotionsTemplate">
                            <ng-template
      #externalPromotionsTemplate
      let-context="context"
      let-externalPromotionsData="externalPromotionsData"
      let-trackClick="trackClick"
    >
      <div
        class="searchstax-external-promotions-container"
        id="searchstax-external-promotions-container"
      >
        <div
          class="searchstax-external-promotion searchstax-search-result"
          *ngFor="
            let externalPromotion of externalPromotionsData.externalPromotions
          "
        >
          <div class="icon-elevated"></div>
          <a
            *ngIf="externalPromotion.url"
            [href]="externalPromotion.url"
            (click)="trackClick(externalPromotion, $event, context)"
            class="searchstax-result-item-link"
          ></a>
          <div class="searchstax-search-result-title-container">
            <span class="searchstax-search-result-title">{{
              externalPromotion.name
            }}</span>
          </div>
          <p
            *ngIf="externalPromotion.description"
            class="searchstax-search-result-description searchstax-search-result-common"
          >
            {{ externalPromotion.description }}
          </p>
          <p
            *ngIf="externalPromotion.url"
            class="searchstax-search-result-description searchstax-search-result-common"
          >
            {{ externalPromotion.url }}
          </p>
        </div>
      </div>
    </ng-template>
          </app-searchstax-external-promotions>

Sorting Widget

The SearchStax Site Search solution provides an Angular sorting widget for your custom search page.

The SearchstaxSortingWidget displays sorting options for search results.

Usage

<app-searchstax-search-sorting></app-searchstax-search-sorting>

Props

  • templateOverride - template override for sorting widget

Main Template Override

Main template for sorting widget.

It receives following props:

  • sortingData – Sorting data object
  • orderChange – Handler for sorting change
  • selectedSorting – Current selected sorting
  • templateOverride - template override for sorting widget

Example

<app-searchstax-search-sorting [templateOverride]="sortingTemplateOverride">
                           <ng-template
      #sortingTemplateOverride
      let-selectedSorting="selectedSorting"
      let-orderChange="orderChange"
      let-context="context"
    >
      <div class="searchstax-sorting-container">
        <label
          class="searchstax-sorting-label"
          for="searchstax-search-order-select"
          >Sort By</label
        >
        <select
          id="searchstax-search-order-select"
          class="searchstax-search-order-select"
          [(ngModel)]="context.selectedSorting"
          (change)="orderChange($event, context)"
        >
          <option
            *ngFor="let sortOption of context.sortingData.sortOptions"
            [value]="sortOption.key"
          >
            {{ sortOption.value }}
          </option>
        </select>
      </div>
    </ng-template>
        </app-searchstax-search-sorting>

Template overrides

Templates use vue templating.

STYLING

scss styles can be imported from searchstudio-ux-js

 @import './../node_modules/@searchstax-inc/searchstudio-ux-js/dist/styles/scss/mainTheme.scss';

css can be taken from

./../node_modules/@searchstax-inc/searchstudio-ux-js/dist/styles/mainTheme.css