Skip to content

Repository files navigation

@mango-iiif/collection-navigator

Framework-free APIs and a web component for turning IIIF Presentation 2 or 3 Collections and Manifests into a navigable tree. The package has no application-framework runtime dependency.

It supports:

  • Presentation 3 Collection.items and Presentation 2 collections, manifests, and members
  • Manifest structures/Ranges, with Canvas fallback when a table of contents is absent
  • lazy retrieval of referenced child Collections
  • complete, serializable JSON output
  • hierarchy and date views with explicit date coverage and opt-in metadata discovery
  • a collapsible, accessible sidebar custom element
  • typed selection events and an optional Mango integration helper

Install

npm install @mango-iiif/collection-navigator

Component

Importing the package registers <mango-collection-tree>:

import '@mango-iiif/collection-navigator';
<mango-collection-tree
  src="https://wellcomelibrary.org/iiif/collection/b19974760"
></mango-collection-tree>

The root resource loads immediately. A referenced child Collection that only has an id, type, and label loads when the user expands it. Embedded child Collections are available without extra requests. Only expanded branches are added to the DOM, which keeps large archives responsive.

Derived date and hierarchy navigation views are cached until the IIIF structure changes. Initial preparation and remote child lookups show a spinner; expanding an already loaded group reuses the cached view.

The hierarchy view preserves the source Collection order and nesting without dereferencing every Manifest. Date view groups dates already advertised by the Collection and reports coverage. A shallow Manifest whose date has not been fetched appears under Date not loaded; only a fetched Manifest with no date appears under Undated. A shallow Manifest remains selectable and also has a disclosure control: expanding it lazily loads its Ranges or Canvases.

Date discovery is collection-only by default. Applications can opt into bounded adaptive discovery, a user action, or explicit eager discovery:

<mango-collection-tree
  date-discovery="adaptive"
  date-discovery-limit="50"
  date-discovery-concurrency="4"
></mango-collection-tree>

Allowed values are collection-only (default), adaptive, on-demand, and all. Adaptive mode automatically discovers dates only when the currently known missing set is at or below the configured limit, defaulting to 50. Larger sets require an explicit action and load in batches of at most 100. The date tab shows a throbber during discovery and remains disabled until at least one real date is known. Applications can inspect navigation.dateCoverage or call loadMissingDates({ limit, concurrency }) directly when they have collection-specific knowledge.

Choose an initial mode from markup or JavaScript:

<mango-collection-tree src="https://example.org/collection" sort-mode="volume"></mango-collection-tree>
tree.sortMode = 'date';
console.log(tree.navigation); // serializable NavigationView

The component defaults to the hierarchy view. Date becomes selectable only when at least one Manifest has a known date; unresolved and genuinely undated sets do not enable an empty date view. The public mode value remains volume for compatibility.

Use the generic element alias when the input is already JSON:

import type { CollectionTreeElement } from '@mango-iiif/collection-navigator';

const tree = document.querySelector<CollectionTreeElement>('mango-collection-tree')!;
await tree.load(manifestJson);

const snapshot = tree.toJSON();
console.log(snapshot?.root.children);

Data-only API

Use the core entry point in a browser, Node, a worker, or an application-owned UI:

import { createNavigationView, loadIIIF, parseIIIF } from '@mango-iiif/collection-navigator/core';

const embedded = parseIIIF(manifestJson);
const lazy = await loadIIIF('https://example.org/iiif/collection');
const resolved = await loadIIIF('https://example.org/iiif/collection', {
  resolve: 'all',
  concurrency: 4,
  maxDepth: 5,
});

console.log(JSON.stringify(resolved));
console.log(createNavigationView(resolved, { mode: 'date' }));

For progressive control, retain a controller:

import { CollectionController } from '@mango-iiif/collection-navigator/core';

const controller = new CollectionController();
const structure = await controller.load(collectionUrl);

const referencedCollection = structure.root.children[0];
if (referencedCollection) await controller.resolve(referencedCollection);

controller.on('collection-progress', ({ detail }) => {
  console.log(`${detail.loaded} loaded; ${detail.pending} pending`);
});

await controller.resolveAll({ concurrency: 4, maxDepth: 3 });
const json = controller.toJSON();

loadIIIF() is lazy by default. resolveAll() follows referenced Collections but not thousands of Manifest leaves. Pass resolveManifests: true only when a complete archive-wide Range/Canvas snapshot is genuinely required.

Application integration

The parser, controller, navigation model, and component do not import Mango or any other viewer. The host application owns the response to user selection.

The component emits a bubbling, composed iiif-collection-select event with this shape:

type CollectionSelection = {
  node: StructureNode;
  manifestId?: string;
  canvasId?: string;
};

Listen with the exported event constant so the integration does not depend on a string literal:

import {
  collectionSelectEvent,
  type CollectionSelection,
} from '@mango-iiif/collection-navigator/core';

tree.addEventListener(collectionSelectEvent, ((event: CustomEvent<CollectionSelection>) => {
  const { manifestId, canvasId } = event.detail;
  application.openIIIFResource({ manifestId, canvasId });
}) as EventListener);

Component-owned text accepts a partial JSON-compatible messages object. Set it before src or load() when configuring the element; unspecified values use the exported English defaults:

tree.locale = 'fr';
tree.messages = {
  viewBy: 'Afficher :',
  hierarchy: 'hiérarchie',
  date: 'date',
  loadingStructure: 'Chargement de la structure IIIF…',
};
tree.src = collectionUrl;

Import defaultCollectionTreeMessages to inspect all available keys. Templates use placeholders such as {count}, {label}, and {error}.

Applications may also use a different custom-element name. Import the side-effect-free tree entry point rather than the auto-registering root entry point:

import {
  defineCollectionTree,
  type CollectionTreeElement,
} from '@mango-iiif/collection-navigator/tree';

defineCollectionTree('iiif-collection-tree');

const tree = document.querySelector<CollectionTreeElement>('iiif-collection-tree')!;

The core, navigation, and tree entry points are viewer-neutral. The package has no runtime dependencies.

Optional Mango adapter

Mango support is isolated in the explicit @mango-iiif/collection-navigator/mango subpath and is not exported from the default package entry point. The adapter itself uses structural typing and does not import Mango at runtime:

import { Mango } from '@mango-iiif/iiif-viewer';
import '@mango-iiif/collection-navigator';
import { bindCollectionToMango } from '@mango-iiif/collection-navigator/mango';

const viewer = new Mango({
  target: document.querySelector('#viewer')!,
});
const tree = document.querySelector('mango-collection-tree')!;

const unbind = bindCollectionToMango(tree, viewer);

// Later:
unbind();
viewer.destroy();

For a Manifest selection the adapter calls viewer.setManifest(manifestId). For a Canvas selection it waits until the new Manifest has Canvases, then calls viewer.setCanvasById(canvasId). This wait matters because Mango fetches a newly selected Manifest asynchronously.

Svelte

The component works as a normal custom element:

<script lang="ts">
  import '@mango-iiif/collection-navigator';
  import type { CollectionSelection } from '@mango-iiif/collection-navigator/core';

  let collectionUrl = 'https://example.org/iiif/collection';
  const select = (event: CustomEvent<CollectionSelection>) => {
    console.log(event.detail.manifestId);
  };
</script>

<mango-collection-tree
  src={collectionUrl}
  on:iiif-collection-select={select}
></mango-collection-tree>

Styling

The shadow-DOM component exposes CSS variables and parts:

mango-collection-tree {
  --mango-collection-accent: #0f766e;
  --mango-collection-background: #fff;
  --mango-collection-surface: #f0fdfa;
  --mango-collection-text: #11201e;
  --mango-collection-muted: #52635f;
  --mango-collection-border: #b9cfca;
  --mango-collection-radius: 0;
  --mango-collection-max-height: 80vh;
}

Public parts are sidebar, header, navigation, sort-date, sort-volume, date-discovery, tree-container, toggle, item, and label. The sidebar has no default resource title; a host can add one through the optional heading slot.

See docs/API.md for the complete data model and event list.

Development

The main demo uses the development-only openseadragon dependency to display a selected Manifest. That viewer integration lives entirely under demo/; the library source and published runtime remain viewer-neutral.

npm install
npm run dev
npm run typecheck
npm test
npm run build
npm run pack:check

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages