Refresh UI components and showcase; release updated framework packages
This commit is contained in:
1 parent
d800228585
commit
c77f5e1cec
293 files changed
+22544
-35778
No files matched your search
@@ -1,5 +1,9 @@
|
||||
# @wrnexus/viewer
|
||||
|
||||
Use `hideName={true}` on `<Viewer>` (or `{ hideName: true }` in `mountViewer`)
|
||||
when the surrounding app already displays the filename. Defaults to false;
|
||||
accessible labels, file-type detection and download filenames are preserved.
|
||||
|
||||
Complete implementation of the
|
||||
[file viewer plan](../../docs/plans/2026-09-08-file-viewer-design.md).
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ component Viewer {
|
||||
props {
|
||||
files: unknown[] = []
|
||||
richPdf: boolean = false
|
||||
hideName: boolean = false
|
||||
fullScreen: boolean = false
|
||||
layout: string = "inline"
|
||||
src: string = ""
|
||||
@@ -16,8 +17,8 @@ component Viewer {
|
||||
class: string = ""
|
||||
}
|
||||
view {
|
||||
<section class="wrn-viewer {class}" data-theme="{theme === 'auto' ? '' : theme}" data-wrnexus-runtime="viewer" data-files="{JSON.stringify(files)}" data-rich-pdf="{richPdf}" data-full-screen="{fullScreen}" data-layout="{layout}" data-src="{src}" data-key="{key}" data-store="{store}" data-name="{name}" data-mime="{mime}" data-size="{size}" aria-label="{name === '' ? 'File preview' : name}">
|
||||
<ViewerShell name={name} />
|
||||
<section class="wrn-viewer {class}" data-theme="{theme === 'auto' ? '' : theme}" data-wrnexus-runtime="viewer" data-files="{JSON.stringify(files)}" data-rich-pdf="{richPdf}" data-hide-name="{hideName}" data-full-screen="{fullScreen}" data-layout="{layout}" data-src="{src}" data-key="{key}" data-store="{store}" data-name="{name}" data-mime="{mime}" data-size="{size}" aria-label="{name === '' ? 'File preview' : name}">
|
||||
<ViewerShell name={name} hideName={hideName} />
|
||||
<noscript>Enable JavaScript to preview this file.</noscript>
|
||||
</section>
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@ import ArchiveViewer from "@wrnexus/viewer/components/ArchiveViewer.wrn"
|
||||
component ViewerShell {
|
||||
props {
|
||||
name: string = "File"
|
||||
hideName: boolean = false
|
||||
class: string = ""
|
||||
}
|
||||
view {
|
||||
<div class="wrn-viewer-shell {class}">
|
||||
<ViewerToolbar name={name} />
|
||||
<ViewerToolbar name={name} hideName={hideName} />
|
||||
<div class="wrn-viewer-content" data-viewer-content=""><p role="status">Loading preview…</p></div>
|
||||
<template data-viewer-template="DownloadCard"><DownloadCard /></template>
|
||||
<template data-viewer-template="ImageViewer"><ImageViewer /></template>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
component ViewerToolbar {
|
||||
props {
|
||||
name: string = "File"
|
||||
hideName: boolean = false
|
||||
class: string = ""
|
||||
}
|
||||
view {
|
||||
<div class="wrn-viewer-toolbar {class}" data-viewer-toolbar="" role="toolbar" aria-label="File preview controls">
|
||||
<span data-viewer-title="">{name}</span>
|
||||
<span data-viewer-title="" hidden={hideName}>{name}</span>
|
||||
<span data-viewer-controls=""></span>
|
||||
<span data-viewer-actions=""></span>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/viewer",
|
||||
"version": "0.8.12",
|
||||
"version": "0.8.13",
|
||||
"type": "module",
|
||||
"sideEffects": true,
|
||||
"description": "Read-only file previews with bounded loading and graceful fallback.",
|
||||
|
||||
@@ -27,6 +27,8 @@ function formatBytes(bytes: number): string {
|
||||
|
||||
/** Mount a preview, including local File/Blob sources. Dispose before reusing the host. */
|
||||
export interface MountViewerOptions {
|
||||
/** Hide the visual filename while retaining accessible labels and download names. */
|
||||
hideName?: boolean;
|
||||
richPdf?: boolean;
|
||||
/**
|
||||
* Opens covering the page, with a Close control that returns it inline. This
|
||||
@@ -83,6 +85,8 @@ export function mountViewer(
|
||||
toolbar.querySelector<HTMLElement>("[data-viewer-title]") ??
|
||||
element("span", source.name || "File");
|
||||
title.textContent = source.name || "File";
|
||||
title.hidden = options.hideName === true;
|
||||
title.setAttribute('data-viewer-title','');
|
||||
if (!toolbar.contains(title)) toolbar.append(title);
|
||||
const controls = toolbar.querySelector<HTMLElement>("[data-viewer-controls]") ?? element("span");
|
||||
const actions = toolbar.querySelector<HTMLElement>("[data-viewer-actions]") ?? element("span");
|
||||
@@ -399,6 +403,7 @@ export function startViewerRuntime(root: Document = document): () => void {
|
||||
const signature = [
|
||||
host.dataset.files,
|
||||
host.dataset.richPdf,
|
||||
host.dataset.hideName,
|
||||
host.dataset.fullScreen,
|
||||
host.dataset.layout,
|
||||
host.dataset.src,
|
||||
@@ -425,6 +430,7 @@ export function startViewerRuntime(root: Document = document): () => void {
|
||||
},
|
||||
{
|
||||
richPdf: host.dataset.richPdf === "true",
|
||||
hideName: host.dataset.hideName === "true",
|
||||
fullScreen: host.dataset.fullScreen === "true",
|
||||
layout: host.dataset.layout === "page" ? "page" : "inline",
|
||||
},
|
||||
@@ -442,6 +448,7 @@ export function startViewerRuntime(root: Document = document): () => void {
|
||||
attributes: true,
|
||||
attributeFilter: [
|
||||
"data-full-screen",
|
||||
"data-hide-name",
|
||||
"data-layout",
|
||||
"data-files",
|
||||
"data-rich-pdf",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import manifest from "../package.json";
|
||||
|
||||
test("published viewer retains browser auto-mount side effects", () => {
|
||||
// The runtime registers renderers and installs DOM/navigation listeners on import.
|
||||
// Marking split published chunks side-effect-free drops those initializers.
|
||||
expect(manifest.sideEffects).toBe(true);
|
||||
});
|
||||
@@ -12,6 +12,16 @@ function fixture() {
|
||||
window.document.body.append(host);
|
||||
return { window, host: host as unknown as HTMLElement };
|
||||
}
|
||||
|
||||
test('hideName hides only the visual title and preserves the file identity',async()=>{
|
||||
const {window,host}=fixture();
|
||||
const dispose=mountViewer(host,{name:'report.txt',blob:new Blob(['hello'])},{hideName:true});
|
||||
await settled();
|
||||
expect(host.querySelector<HTMLElement>('[data-viewer-title]')?.hidden??host.querySelector<HTMLElement>('.wrn-viewer-toolbar span')?.hidden).toBe(true);
|
||||
expect(host.getAttribute('aria-label')).toBe('report.txt');
|
||||
expect(host.querySelector<HTMLAnchorElement>('a[download]')?.download).toBe('report.txt');
|
||||
dispose();window.close();
|
||||
});
|
||||
test("image controls, failure fallback, and teardown", async () => {
|
||||
const { window, host } = fixture();
|
||||
const dispose = mountViewer(host, {
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.wrn-viewer-toolbar > [data-viewer-title][hidden] { display: none; }
|
||||
.wrn-viewer-toolbar:has(> [data-viewer-title][hidden]) { justify-content: flex-end; }
|
||||
/* The generic control look applies only to controls the package has not styled
|
||||
itself; without this it outranks every specific rule below. */
|
||||
.wrn-viewer button:not([class*="wrn-viewer-"]),
|
||||
@@ -903,9 +905,9 @@
|
||||
/* `Viewer.wrn` renders the toolbar and content inside a shell, so the growing
|
||||
box is two levels down: every wrapper between has to flex for the content to
|
||||
fill the space these modes hand it. */
|
||||
.wrn-viewer-dialog .wrn-viewer > *:not(noscript),
|
||||
.wrn-viewer-expanded > *:not(noscript),
|
||||
.wrn-viewer-as-page > *:not(noscript),
|
||||
.wrn-viewer-dialog .wrn-viewer > *:not(noscript, .wrn-viewer-toolbar, .wrn-viewer-content),
|
||||
.wrn-viewer-expanded > *:not(noscript, .wrn-viewer-toolbar, .wrn-viewer-content),
|
||||
.wrn-viewer-as-page > *:not(noscript, .wrn-viewer-toolbar, .wrn-viewer-content),
|
||||
.wrn-viewer-dialog .wrn-viewer-shell,
|
||||
.wrn-viewer-expanded .wrn-viewer-shell,
|
||||
.wrn-viewer-as-page .wrn-viewer-shell {
|
||||
|
||||
Reference in new issue
Block a user