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

lily-design-system-angular-share-picker

v0.2.0

Published

Lily Design System Angular 20 share picker: an icon button opening the native share sheet, or a disclosure of consumer-supplied links plus copy-to-clipboard. Headless, SSR-safe, no CSS.

Readme

SharePicker (Angular helper)

A headless Angular 20 share control: a single-glyph button (➤) that opens the native share sheet where the browser has one, and otherwise shows a list of destinations you supply, plus copy the page URL.

The single source of truth is spec/index.md. This file is the human-readable guide.

Install

import {
  SharePicker,
  SharePickerIcon,
  canShareNatively,
  type ShareTarget,
} from "./lily-design-system-angular-share-picker";

SharePicker is a standalone component — add it to a component's imports, not to an NgModule.

Quick start

import { ChangeDetectionStrategy, Component } from "@angular/core";
import {
  SharePicker,
  type ShareTarget,
} from "../lily-design-system-angular-share-picker";

@Component({
  selector: "app-article",
  standalone: true,
  imports: [SharePicker],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <lily-share-picker
      label="Share this page"
      title="An article worth reading"
      [targets]="targets"
      copyLabel="Copy link"
      copiedLabel="Link copied"
      copyFailedLabel="Could not copy — copy it from the address bar"
      (share)="onShare($event)"
    />
  `,
})
export class ArticleComponent {
  readonly targets: ShareTarget[] = [
    {
      id: "mastodon",
      label: "Mastodon",
      href: (url, title) =>
        `https://mastodon.social/share?text=${encodeURIComponent(title)}%20${encodeURIComponent(url)}`,
    },
    {
      id: "email",
      label: "Email",
      href: (url, title) =>
        `mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(url)}`,
      newTab: false,
    },
  ];

  onShare(event: { targetId: string; url: string }): void {
    console.log(event.targetId, event.url);
  }
}

url defaults to the current page, so the common case needs no wiring.

You supply the destinations

This package ships no social-network URLs. That is deliberate: which networks belong in your product is an editorial and privacy decision, the share endpoints change, and networks die. You pass targets, so the labels localise with the rest of your copy and no third-party endpoint is baked into a design system.

href is a function, so you own the whole URL and its encoding:

{ id: "linkedin", label: "LinkedIn",
  href: (url) => `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}` }

Native share sheet

With strategy="auto" (the default), pressing the button on a device with navigator.share opens the OS sheet — the user gets their real installed apps, and nothing is disclosed to a third party by the act of opening it. Where there is no sheet, the list opens instead.

This means behaviour differs by platform, which is worth knowing when you write help text or test scripts. Force one path with strategy="list" or strategy="native".

A dismissed sheet ends the interaction — the list does not then pop open, which would resurrect UI the user just dismissed.

Copy to clipboard

Supply copyLabel and a copy item appears. There is no default label, because a default would be a hardcoded English string. copiedLabel and copyFailedLabel are announced in a polite live region — copying is otherwise silent, so without them the user gets no confirmation.

Failure is handled, not assumed away: a denied permission, an insecure context, or a browser with no async clipboard all announce copyFailedLabel rather than throwing.

Why links, not a menu

Destinations render as real <a> elements, not role="menuitem". A menuitem role strips middle-click, open-in-new-tab, and copy-link-address — affordances users genuinely reach for on a share list. The WAI-ARIA APG suggests a disclosure when the items are links. Copy is a real action, so it is a <button>.

Custom glyph

Project an <ng-template> to replace the ➤ glyph inside the trigger. The optional SharePickerIcon marker directive gives the let- variable a type:

<lily-share-picker label="Share" [targets]="targets">
  <ng-template lilySharePickerIcon let-args>
    {{ args.open ? "Close" : "Share" }}
  </ng-template>
</lily-share-picker>

The context is ChildArgs{ open, url } — available both as $implicit and as named properties.

Inputs and outputs

Full table in spec/index.md §4.1. Required: label. Everything else is optional.

Outputs are Angular output()s rather than callback inputs: (share) emits { targetId, url }, (copy) emits the URL, and (nativeShare) emits the URL.

Accessibility

  • The glyph is aria-hidden; the name comes from aria-label, which also names the list, so a screen reader entering it hears what it is for.
  • Escape closes and returns focus to the trigger; arrows move between items and clamp; Home / End jump; Tab closes via the trigger so the default Tab proceeds from the picker's position.
  • The status region is polite and empty on load.
  • Tradeoff: an icon-only control's name rests entirely on aria-label — there is no visible text fallback. See docs/accessibility.md.

Styling

Class hooks: .share-picker (root), .share-picker-button, .share-picker-icon, .share-picker-list, .share-picker-list-item, .share-picker-target, .share-picker-copy, .share-picker-status.

The package ships no CSS. The root themes/ stylesheets style the button and popup, including the optical glyph sizing that keeps ➤ visually the same size as the other helpers' glyphs.

Tests

npx vitest run lily-design-system-angular-share-picker from the catalog root — 49 cases, one or more per §7 clause.

See also

License

Dual-licensed under MIT or Apache-2.0 or GPL-2.0 or GPL-3.0 or BSD-3-Clause. Contact [email protected] for other terms.


Lily™ and Lily Design System™ are trademarks.