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

@wincc-oa/wui-shared

v2.0.1

Published

WinCC Open Architecture Dashboard project.

Readme

WinCCOA WebComponent Dashboard

This package is part of the workspace for the WinCC Open Architecture WebComponent Dashboard, built using Lit and managed with Nx.

Usage information and reference details can be found in the WinCC OA documentation.

wui-cleanup-service

The wui-cleanup-service provides centralized resource cleanup during application lifecycle events such as user logout, session timeout, or application shutdown. It automatically discovers cleanable services and executes registered cleanup handlers.

The cleanup service follows a two-phase cleanup approach:

  1. Service Discovery: Automatically finds services extending WuiCleanableService
  2. Handler Execution: Runs registered cleanup handlers for custom cleanup logic

Creating a Cleanable Service

import { WuiCleanableService } from '@wincc-oa/wui-shared/services/wui-cleanup/wui-cleanable-service.abstract.js';
import { singleton } from 'tsyringe';

@singleton()
export class UserSessionService extends WuiCleanableService {
  /**
   * Performs service-specific cleanup operations.
   * Called automatically during application lifecycle cleanup.
   *
   * @returns Promise resolving to true if cleanup succeeded
   */
  async cleanup(): Promise<boolean> {
    try {
      localStorage.removeItem('userSession');
      sessionStorage.removeItem('tempData');

      console.log('UserSessionService cleanup completed');
      return true;
    } catch (error) {
      console.error('UserSessionService cleanup failed:', error);
      return false;
    }
  }
}

Handler Registration

Register custom cleanup handlers for operations that don't fit into service cleanup:

NOTE!

All registered handlers are removed after cleanup.

import { WuiCleanupService } from '@wincc-oa/wui-shared/services/wui-cleanup/wui-cleanup.service.js';
import { container } from 'tsyringe';

/**
 * Registers various cleanup handlers for different application resources.
 */
export function registerApplicationCleanupHandlers(): void {
  const cleanupService = container.resolve<WuiCleanupService>(WuiCleanupService);

  // Handler with no arguments - simple cache clearing
  cleanupService.registerCleanupHandler(async () => {
    console.log('Clearing application localStorage');
    localStorage.clear();
  });

  // Async handler
  cleanupService.registerCleanupHandler(
    async (userId: string, sessionId: string) => {
      await fetch('/api/cleanup', {
        body: JSON.stringify({ userId, sessionId }),
        headers: { 'Content-Type': 'application/json' }
      });
    },
    ['current-user-id', 'current-session-id']
  );
}

// Register handlers during application initialization
registerApplicationCleanupHandlers();

Executing Cleanup

import { WuiCleanupService } from '@wincc-oa/wui-shared/services/wui-cleanup/wui-cleanup.service.js';
import { container } from 'tsyringe';

/**
 * Executes cleanup during user logout.
 * Discovers and cleans up all registered services and handlers.
 */
export async function handleUserLogout(): Promise<void> {
  const cleanupService = container.resolve<WuiCleanupService>(WuiCleanupService);

  try {
    const cleanupSuccess = await cleanupService.cleanup();

    if (cleanupSuccess) {
      console.log('Logout cleanup completed successfully');
      // Redirect to login page
      window.location.href = '/login';
    } else {
      console.warn('Some cleanup operations failed during logout');
      // Handle partial cleanup failure
    }
  } catch (error) {
    console.error('Critical error during logout cleanup:', error);
  }
}

BeforeLeaveController

Blocks in-app navigation before it happens — the canonical way to guard a page with unsaved changes (dirty form, editor session).

It works because the router commits the URL only after the navigateTo event reaches the window-level listener. BeforeLeaveController (via NavigateToController) listens on document, which is earlier in the bubble path, so calling stopPropagation() there cancels the navigation cleanly — the URL is never committed and screen and address bar stay in sync.

class MyEditor extends LitElement {
  private isDirty = false;

  constructor() {
    super();
    new BeforeLeaveController(this, (event) => this.onBeforeLeave(event));
  }

  private onBeforeLeave(event: RouterEvent | BeforeUnloadEvent): void {
    if (!this.isDirty) return; // let navigation proceed
    if (event instanceof RouterEvent) {
      if (event.detail.userConfirmed) {
        this.isDirty = false; // second pass after the user confirmed
      } else {
        event.stopPropagation(); // cancel this navigation
        void this.confirmThenRenavigate(event.detail);
      }
    }
  }
}

Confirm asynchronously, then re-dispatch the same RouterEvent with userConfirmed: true to let it through. See wui-dashboard-edit for the full reference pattern.

This is the ONLY way to cancel navigation. The <wui-router-outlet> lifecycle hooks (onBeforeLeave, etc.) run after the URL is committed and are notify-only — their return values cannot stop a navigation. See the wui-router README.

BeforeUnloadController (browser tab close / reload) is currently disabled: no clean way was found to avoid conflicting with the beforeunload listener in oa-rx-js-api. Only the in-app navigateTo path is guarded today.

outletInject

outletInject is a property decorator and a drop-in replacement for container.resolve(TOKEN) in a field initializer. It works on any LitElement (ReactiveControllerHost & HTMLElement). Signature: outletInject<T>(token: InjectionToken<T>).

The decorator replaces the field with a lazy getter. On each access it resolves the token from the nearest router outlet's child container, falling back to the global container when the host is used outside any outlet. Resolution happens per access through a per-host OutletContainerController (created once per element), not once at construction, so the value tracks the outlet the host is currently mounted in.

class MyPage extends LitElement {
  @outletInject(WuiRouterFacadeToken)
  private readonly routerService!: WuiRouterFacade;

  render() {
    // read post-connect: resolves the current outlet's router view
    const path = this.routerService.location.pathname;
    const tab = this.routerService.getSearchParam('tab');
    return html`<div>${path} (tab: ${tab})</div>`;
  }
}

When it matters

outletInject differs from container.resolve() for any token an outlet registers per-outlet in its child container. For such a token the decorator resolves the outlet-scoped value instead of the global one. For example, the router registers an outlet-scoped router view under WuiRouterFacadeToken, so a component in a side outlet reads that outlet's router state instead of main's. See the routing concept in the wui-router README.

For plain global singletons (tokens no outlet overrides), resolving through the child container returns the same instance as the global container, so outletInject is behaviorally identical to container.resolve(). Use it for outlet-scoped tokens; for pure globals it is future-proofing, not a behavior change.

How a token becomes outlet-scoped

A token gains an outlet-scoped value on the registration side in one of two ways, and @outletInject consumes both identically:

  1. The outlet registers a value per child container (child.register(TOKEN, { useValue: ... })), as the router does for WuiRouterFacadeToken.
  2. A service marks itself @scoped(Lifecycle.ContainerScoped) (from tsyringe), so tsyringe creates one instance per child container automatically.

Either way, a component reads it the same way with @outletInject(TOKEN):

import { scoped, Lifecycle } from 'tsyringe';

@scoped(Lifecycle.ContainerScoped)
class MyOutletState {}

class MyPanel extends LitElement {
  @outletInject(MyOutletState)
  private readonly state!: MyOutletState;
}

For the outlet-scoping concept and the router registration example, see the wui-router README.

The sharp edge: read post-connect

The outlet child container is published via @lit/context, and the OutletContainerController's ContextConsumer connects in hostConnected. The outlet container is therefore only available at or after connectedCallback.

Reading an outletInject property in a constructor, or in the field initializer of another field (both run before connect), resolves from the global container, not the outlet's. Because the getter re-resolves lazily, reading the same property later returns the correct outlet instance.

Rule: read outlet-scoped injected properties post-connect (in render, event handlers, or firstUpdated), never in the constructor.

License

MIT