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

@myrmidon/cadmus-thesaurus-store

v0.0.6

Published

Cadmus thesauri store components.

Readme

CadmusThesaurusStore

This project was generated using Angular CLI version 21.0.0.

This library contains components related to Cadmus thesauri and using as their core the paged tree browser from @myrmidon/paged-data-browsers.

Thesauri

Thesauri represent taxonomies of any type you might be using in editing your content. For instance, in describing a writing you might have a set of ink colors to pick from. The models for the thesaurus and its entries are defined in an external package (@myrmidon/cadmus-core).

Essentially, a thesaurus is a list of terms, each having an ID and a human-readable value. Such list can be a simple, flat list, or a hierarchical list. In both cases, they are defined as objects having each a list of entries.

Each entry in a thesaurus object has:

  • an arbitrarily assigned id, which must be unique in the context of that object;
  • a text value to be displayed to the end user.

For instance, you might have a list of languages to pick from, like English, French, Italian, etc.: in this case, your thesaurus would have its own ID, e.g. languages, plus entries like eng=English, fre=French, ita=Italian, etc. Here I'm using ISO 639 for language codes, and using standards is always recommended when available; but IDs are totally arbitrary.

You can also have hierarchical thesauri, where entries are arranged in a hierarchy represented with dots in their IDs. For instance, say you want to represent this simple 2-levels hierarchy:

  • language:
    • language: phonology
    • language: morphology
    • language: syntax

You can use a dot in each entry to represent three children entries under the same node:

[
  { "id": "lang.pho", "value": "language: phonology" },
  { "id": "lang.mor", "value": "language: morphology" },
  { "id": "lang.syn", "value": "language: syntax" }
]

By convention, for each dot in the ID there is a colon in the value, so that the full hierarchy is displayed in every entry.

Should you want to have a selectable entry also for the parent language node, you just have to add another one, like this:

[
  { "id": "lang.-", "value": "language" },
  { "id": "lang.pho", "value": "language: phonology" },
  { "id": "lang.mor", "value": "language: morphology" },
  { "id": "lang.syn", "value": "language: syntax" }
]

Services

A couple of services are used by the components in this library:

  • StaticThesaurusPagedTreeStoreService, which represents an in-memory store of the thesaurus being displayed. This service serves as an adapter from an array of thesaurus entries to the paged tree store service. Its nodes are of type ThesaurusEntryPagedTreeNode and its filter is of type ThesaurusEntryNodeFilter.
  • EditableStaticThesaurusPagedTreeStoreService, which is the editable counterpart of StaticThesaurusPagedTreeStoreService. It still loads all the entries of a thesaurus at once (whence "static" in its name), but it can keep track of user edits and then commit all changes at once.

Components

The higher-level components exported by this library are listed here.

ThesaurusTreeComponent

  • 🔑 cadmus-thesaurus-tree

The thesaurus tree component displays a set of hierarchical thesaurus entries in a tree, provided that each entry marks its hierarchy with dots, and allows you to pick any entry.

  • ▶️ input:
    • entries: all the entries in the thesaurus.
    • renderLabel: the optional function for rendering node's labels. This is typically used to shorten the label of nested entries when displaying them.
  • 🔥 output:
    • entryChange: fired when an entry is picked.

Internally, this component uses:

  • an instance of the StaticThesaurusPagedTreeStoreService.
  • an instance of the ThesaurusBrowserComponent, which is a readonly paged tree browser specialized to display thesaurus entries.

ThesaurusEntriesPickerComponent

  • 🔑 cadmus-thesaurus-entries-picker

This is a more compact and powerful component which allows you to manage entries picked from a hierarchical thesaurus, which is usually collapsed so that the component uses just as much space as needed to display a list of picked entries. Each picked entry can be directly removed from the list, and new entries can be added by expanding the tree and picking them.

The component also allows users to enter custom entries beyond the thesaurus entries. This is a special functionality which must be opted-in. Such entries are all marked by an initial $ prefix and do not belong to a thesaurus.

  • ▶️ input:
    • availableEntries: all the entries in the thesaurus. Required.
    • entries: the entries picked.
    • hierarchicLabels: true to show the entries with labels shortened according to their hierarchy.
    • autoSort: true to automatically sort the picked entries (disables drag-and-drop). When false, entries can be manually reordered via drag-and-drop.
    • allowCustom: true to allow custom values (not in the entries list).
    • minEntries: the minimum number of picked entries.
    • maxEntries: the maximum number of picked entries.
    • expanded: true when the picker is expanded rather than collapsed.
    • emptyMessage: the message to show when there are no picked entries.
  • 🔥 output:
    • entriesChange: fired when entries change.

EditableThesaurusBrowserComponent

An editable paged tree browser for thesaurus entries. Editing happens in memory and when complete, the changes can be committed to the underlying data store via the EditableStaticThesaurusPagedTreeStoreService.

  • ▶️ input:
    • service: an instance of EditableStaticThesPagedTreeStoreService used to load nodes. Required.
    • hasRootFilter: true to show the tree root nodes filter.
    • nodePick: fired when an entry node is clicked.
  • 🔥 output:
    • changesStateChange: fired when the changes-state changes.

History

0.0.4

  • 2026-01-31: added logging to thesaurus tree browser to diagnose issues in certain scenarios.

0.0.3

  • 2026-01-31: fixes to thesaurus browser and editable thesaurus browser. The ThesaurusTreeComponent wasn't updating when data arrived because of an Angular effect timing issue:

  • The @if (service() && entries()?.length) guard in thesaurus-tree.component.html:3 only creates cadmus-thesaurus-browser once entries has data

  • When the component is created, the effect() in the constructor is scheduled (not run immediately)

  • Angular completes the initial render with empty nodes() signal

  • The effect then runs, loads data, and updates the nodes signal via RxJS subscription callbacks

  • But subscription callbacks run outside Angular's change detection zone, so no re-render is triggered

  • On component close, Angular's final change detection cycle picks up the changes, causing the "flash"

Solution: added ChangeDetectorRef.markForCheck() calls in the subscription callbacks (thesaurus-browser.component.ts:121-137) to explicitly notify Angular when signals are updated from async contexts.

The fix needed to address:

Signal writes inside effect execution: When subscribing to a BehaviorSubject inside an effect(), the subscription callback executes synchronously (BehaviorSubject emits its current value immediately on subscribe). This means signal writes like this.nodes.set(nodes) occur during the effect's execution.

Inconsistent markForCheck() calls: The original fix only added markForCheck() in the effect's subscriptions and the initial load's .finally(), but left other async callbacks (expand, collapse, page change, filter change) without it.

The issue appears in newer Angular apps because they may use:

  • Zoneless change detection (Angular 18+)
  • OnPush change detection as default in project configuration
  • Different timing of change detection cycles

When RxJS subscription callbacks update Angular signals, they run outside Angular's reactive tracking context. Without explicit notification via markForCheck(), Angular may not know to re-render the component.

0.0.2

  • 2026-01-31: fixes to thesaurus browser and editable thesaurus browser replacing ngOnInit with effect and using signals instead of observables to avoid having side effects in computed signals.

0.0.1

  • 2026-01-17:
    • added thesaurus-entries-picker moving it from @myrmidon/cadmus-ui so that all the thesaurus components can migrate into @myrmidon/cadmus-thesaurus-store in this workspace. These components are strictly dependent from the paged tree browser in this workspace, which is a generic component. So, this Cadmus-related library is developed in this workspace, rather than in Cadmus shell workspace, as the components are essentially wrappers around the functionality developed in this workspace.
    • renamed thesauri components (changing "thes..." into "thesaurus..." and shortening where possible).