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

@microsoft/fabric-visuals-extensibility

v4.0.0

Published

Extensibility components for Fabric Apps - Analytics

Readme

fabric-visuals-extensibility

Extensibility components for Fabric Apps - Analytics.

Components

VisualContainer

Wraps any children in a div with a card background and border, an optional title/subtitle header, an optional hover/focus-revealed action toolbar, and a right-click actions menu.

import { VisualContainer } from '@microsoft/fabric-visuals-extensibility';
import type { VisualContainerAction } from '@microsoft/fabric-visuals-extensibility';

const refresh: VisualContainerAction = {
    id: 'refresh',
    label: 'Refresh',
    icon: <RefreshIcon />,
    onClick: () => reload(),
};

function Example() {
    return (
        <VisualContainer header={{ title: 'Sales overview', subtitle: 'Last 12 months' }} customActions={[refresh]}>
            <p>Anything rendered here is wrapped in a bordered container.</p>
        </VisualContainer>
    );
}

Props

| Prop | Type | Description | | ------------------- | ------------------------- | ------------------------------------------------------------------------------------ | | children | ReactNode | Content rendered inside the bordered container. | | header | HeaderProps | Optional header rendered above the content. | | builtInActionIds | BuiltInActionId[] | Built-in actions rendered in the header toolbar by id (e.g. ['copyVisual']). | | customActions | VisualContainerAction[] | Custom action buttons rendered on the trailing edge of the header. | | maxVisibleButtons | number | Max toolbar buttons before extras collapse into a overflow menu. Defaults to 2. | | className | string | CSS class merged onto the container root, alongside the container's own class rather than in place of it. |

HeaderProps:

| Prop | Type | Description | | ---------- | -------- | ------------------------------------------------------------ | | title | string | Title rendered in the header above the content. | | subtitle | string | Optional subtitle rendered beneath the title in the header. |

When no header is provided, no header is rendered and the actions move into a floating toolbar that appears just above the container's top-trailing corner on hover or keyboard focus. It sits outside the container's box, so it never covers the content underneath and is never clipped by the container's own rounding.

Styling the container

className lands on the container root — the element that draws the card background, border, radius, and padding. It is merged with the container's own class (mergeClasses(styles.container, className)), so the built-in chrome stays in place and your class is added on top of it. It does not reach the header, the body region, or the toolbar.

<VisualContainer header={{ title: 'Revenue' }} className="app-card">
    {visual}
</VisualContainer>

Toolbar

customActions render as a toolbar on the trailing edge of the header, opposite the title/subtitle. The toolbar is hover/focus-revealed: it claims no width while idle, then expands in-flow on pointer hover over the container or keyboard focus within it so the header reflows instead of overlaying the title text.

When there are visible actions (built-in + custom, excluding hidden), right-clicking inside the container opens a context menu (role="menu") with the same action list. Selecting an item runs that action and closes the menu. The menu also closes on outside click and Escape. If there are no visible actions, the container does not intercept contextmenu, so the browser's native context menu opens as usual.

VisualContainerAction:

| Prop | Type | Description | | ---------- | ----------------------------- | ---------------------------------------------------------------- | | id | string | Stable id used as the React key. | | label | string | Accessible label used as both the aria-label and the tooltip. | | icon | ReactNode | Icon element rendered inside the button. | | onClick | () => void \| Promise<void> | Invoked on activation. May be async. | | disabled | boolean | When true, the button is rendered disabled. | | hidden | boolean | When true, the button is removed from the toolbar completely. |

BuiltInActionId:

| Id | Label | Behavior | | ------------ | ------ | ------------------------------------------------------------------------- | | copyVisual | Copy | Captures the container as a PNG and writes it to the system clipboard, captioned with the header title and the time of the copy. |

Built-in actions are opt-in by id through builtInActionIds and render ahead of customActions:

<VisualContainer header={{ title: 'Revenue' }} builtInActionIds={['copyVisual']}>{visual}</VisualContainer>

Copy Visual — image capture & clipboard

The built-in copyVisual action and the imperative handle share one capture-and-clipboard path:

  • Capture rasterizes the container to a PNG with modern-screenshot. The library serializes a clone of the DOM, so the live UI stays perfectly still while the capture runs. Any node tagged vc-capture-exclude (exported as CAPTURE_EXCLUDE_ATTR) is dropped from the image — the toolbar is flagged this way so the buttons never land in a copied picture — and the resolved background color keeps exported PNGs opaque.

  • Clipboard writes a single ClipboardItem carrying three MIME flavors of the same copy, so each target can take the richest one it understands:

    | Flavor | Payload | Typical target | | ------------ | -------------------------------------------------------------------------------------------------- | -------------------- | | text/html | an <img> with the PNG inlined as a data: URL, then the title and copy time as two styled <div>s | Word, Outlook, Teams | | text/plain | those same two caption lines, newline separated | editors, chat boxes | | image/png | the capture on its own | image-only targets |

    A container with no header, or one given a React node as its header, has no title to caption with, so the caption is the timestamp alone. The write needs a secure context (https) and a user gesture — the toolbar click is one — and a cross-origin iframe host must also grant allow="clipboard-write".

Both steps are also exported as standalone functions, for the case where there is no VisualContainer at all — a bare visual rendered on its own still needs a supported way to be captured and copied:

| Function | Description | | --------------------------------------------------- | ------------------------------------------------------ | | captureElementAsImage(element: HTMLElement) | Rasterizes any element to a PNG Blob. | | writeImageToClipboard(blob: Blob, caption?: CopyVisualCaption) | Writes an image Blob to the system clipboard, captioned when a caption is given. |

CopyVisualCaption is the caption's lines exactly as they should read — the timestamp is a string, so its locale and format are yours to choose:

| Prop | Type | Description | | ----------- | -------- | -------------------------------------------------------------------------------- | | title | string | The caption's lead line. | | timestamp | string | Already-formatted moment of the copy, rendered beneath the title. |

Either line may be omitted; a caption with neither writes the image alone. The container fills both in for you, stamping the copy time with the viewer's locale. title is treated as plain text, so a title like P&L <Consolidated> pastes as itself; timestamp is written to the HTML flavor as given, so pass formatted text rather than markup.

import { useRef } from 'react';
import { captureElementAsImage, writeImageToClipboard } from '@microsoft/fabric-visuals-extensibility';

const ref = useRef<HTMLDivElement>(null);

async function copyChart() {
    if (!ref.current) return;
    await writeImageToClipboard(await captureElementAsImage(ref.current), {
        title: 'Revenue',
        timestamp: new Date().toLocaleString(),
    });
}

<div ref={ref}>
    <MyVisual />
</div>;

captureElementAsImage accepts any HTMLElement and honors CAPTURE_EXCLUDE_ATTR on its descendants, so you can drop your own chrome out of the image exactly the way the container drops its toolbar. writeImageToClipboard captions the copy the way the container does; omit the caption to write the image alone. It carries the same secure-context and user-gesture requirements described above — call it from an event handler, not from an effect.

This does not replace the container paths, and the hierarchy above still holds. Inside a VisualContainer, builtInActionIds={['copyVisual']} remains the right answer, and the imperative handle is next when you need the capability without the built-in button. Reach for these functions only when there is no container to ask.

Imperative handle

Prefer built-in actions. When a BuiltInActionId already does what you need, opt into it with builtInActionIds: the container renders the button, wires the behavior, and reports failures. That path needs no ref.

The handle exposes the same underlying capabilities the built-ins are implemented with. Use a ref only when the behavior you need is not available as a built-in action — either to build a custom action on top of a container capability, or to drive that capability from outside the container:

| Goal | Use | | ---------------------------------------------------------------- | -------------------------------------- | | Behavior that matches an existing BuiltInActionId | builtInActionIds — no ref | | A custom action needing a capability the container provides | ref + customActions | | Driving a container capability from outside the container | ref |

Do not use a ref to reimplement a built-in as a custom action. If the behavior is already a BuiltInActionId, opt into it instead — hand-rolling a Copy action around copyImageToClipboard, for example, is strictly worse than builtInActionIds={['copyVisual']}.

The example below adds a custom Download PNG action on top of captureAsImage. Downloading has no built-in equivalent, so the handle is the right tool here — while copy, which does have one, still comes from the built-in:

import { useRef } from 'react';
import { VisualContainer } from '@microsoft/fabric-visuals-extensibility';
import type { VisualContainerAction, VisualContainerHandle } from '@microsoft/fabric-visuals-extensibility';

const ref = useRef<VisualContainerHandle>(null);

const actions: VisualContainerAction[] = [{
    id: 'download',
    label: 'Download PNG',
    icon: <DownloadIcon />,
    onClick: async () => {
        const blob = await ref.current?.captureAsImage();
        if (!blob) return;
        const url = URL.createObjectURL(blob);
        Object.assign(document.createElement('a'), { href: url, download: 'chart.png' }).click();
        URL.revokeObjectURL(url);
    },
}];

// `copyVisual` still comes from the built-in — only the download needs the ref.
<VisualContainer
    ref={ref}
    header={{ title: 'Revenue' }}
    builtInActionIds={['copyVisual']}
    customActions={actions}
>
    {visual}
</VisualContainer>;

| Member | Type | Description | | ---------------------- | ------------------------- | -------------------------------------------------- | | element | HTMLElement \| null | The container's root (capture target) element. | | captureAsImage | () => Promise<Blob> | Captures the container as a PNG Blob. | | copyImageToClipboard | () => Promise<void> | Captures the container and writes it to the clipboard, captioned with the header title and the time of the copy. |

When the number of visible actions exceeds maxVisibleButtons, the first maxVisibleButtons - 1 stay inline and the rest collapse into a overflow menu (role="menu"). The menu closes on outside click, Escape, focus moving away, or activating an item.

The border and header are styled with the shared Fabric design tokens, so the container automatically follows the active theme:

| Aspect | Design token | Resolves to | | ------------- | -------------------------- | ------------------ | | Border color | --color-border | theme stroke color | | Corner radius | --radius-md | 4px | | Title text | --color-foreground | theme text color | | Subtitle text | --color-muted-foreground | muted text color |

The border width is a standard 1px hairline.

Toast feedback

The built-in copyVisual action reports its own outcome — the container owns that button, so it owns the feedback:

| Outcome | Toast | Role | Auto-dismiss | | ------- | ------------------------------------------------------------------------ | -------- | ------------ | | Success | Visual copiedThe image is on your clipboard. | status | 4s | | Failure | Couldn't copy this visual — a description that depends on why the copy failed: the clipboard being blocked (a sandboxed or cross-origin frame), unavailable (an insecure page or an unsupporting browser), or anything else. | alert | 10s |

The imperative handle stays silent. copyImageToClipboard() on a ref shows no toast, leaving ref callers free to surface failures through their own notification system.