Webdev 8458 migrate item navigator - #64
Conversation
Port @internetarchive/iaux-item-navigator into src/labs/ia-item-navigator, adopting the elements CSS convention, with a demo and tests. Scope: the navigator shell (ia-item-navigator + ia-menu-slider, ia-menu-button, ia-itemnav-loader, ia-no-theater-available) and the viewable-files menu (ia-viewable-files-panel + ia-sort-files-button). The bundled social share panel is left as a follow-up. - Rename to the ia-* convention (tag ia-item-navigator, class IAItemNavigator); sub-elements prefixed likewise. - Loosen deps: modal-manager and shared-resize-observer were type-only imports, now local interfaces (interfaces/service-interfaces.ts) — zero runtime dep. Keep @internetarchive/metadata-service (real runtime use in the item converter). Inline all icons (ellipses, collapse-sidebar, viewable-files, sort) instead of adding icon-package deps. - CSS convention: public theming vars become --item-navigator-* with --x-- private aliases and inline defaults; themeStyles imported per component; palette tokens replace hardcoded colors where they map. - Fix a latent toggle bug: the menu button bound @click=${this.toggleMenu}, passing the event as forceValue; now wrapped so menuOpened stays boolean. - ia-sort-files-button sorts a copy rather than mutating the reactive prop in place; sort icons use currentColor so they theme with the icon fill var. - Demo story (labs, auto-discovered) wires the real viewable-files panel as a menu provider with the sort button as its action; live style settings. - Vitest browser-mode tests (42) covering drawer, provider rendering, responsive mode, no-theater state, menu selection, file listing, PDF flagging, URL building and sort cycling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complete the migration by porting the social share panel (from
iaux-in-share-panel) as ia-share-panel, so the item-navigator's bundled menus
are fully migrated.
- Rename to ia-share-panel / IASharePanel; export shareIcon as the provider
icon.
- Inline all 7 social/link icons (share, twitter, facebook, tumblr, pinterest,
email, link) as .fill-color templates in menus/share-icons.ts — no
icon-package deps.
- Upgrade the deprecated document.execCommand('copy') to the async Clipboard
API, falling back to execCommand only where the async API is unavailable;
track the copy-note timeout per note via a WeakMap.
- CSS convention: theming vars become --item-navigator-* with --x-- aliases and
inline defaults; base-font-family from themeStyles replaces the hardcoded
font stack.
- Wire the share panel into the demo as a menu provider.
- 5 Vitest tests: option population, share-URL building, embed snippets,
header toggle, and the copy-note flash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move src/labs/ia-item-navigator to src/elements/ia-item-navigator now that the migration is complete, treat it as a production-ready component: - Drop the `labs` flag from the story so it lists under Production-Ready and its import/usage snippets use the @internetarchive/elements/ia-item-navigator path (no /labs segment). - Export the navigator and its consumer-facing menus (viewable-files panel, sort-files button, share panel) from src/elements/index.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #64 +/- ##
==========================================
+ Coverage 78.62% 84.42% +5.80%
==========================================
Files 17 27 +10
Lines 697 1008 +311
Branches 189 255 +66
==========================================
+ Hits 548 851 +303
- Misses 100 108 +8
Partials 49 49 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Fold the useful examples from the upstream demo into the story: - Realistic multi-file item (the "Master Book of American Folk Song" volumes) for the viewable-files panel, including a deliberately long title (wrapping) and PDF entries (PDF flag), with one active file. - A slotted header bar (brand + item title link) and a styled theater mock in slot="main", so the shell's host-content slots are shown in use. - A populated minimized shortcut rail (menuShortcuts) alongside the drawer. - Header, view-available, and fullscreen toggles; fullscreen renders an in-frame "Exit fullscreen" control so the demo can't trap the viewer. - Size shortcut-rail icons consistently with the toggle button, and give viewableFilesIcon the shared .ia-icon class so it themes/sizes like the rest. Kept self-contained: no metadata-service fetch or external image hotlinks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The components' internal sizing was in rem, calibrated for petabox's 10px root font-size (which the upstream demo set on the document). In the elements demo (default 16px root) everything — icons especially — rendered ~1.6x too large. Make the navigator self-contained instead of depending on the consumer's root: - Set font-size: var(--item-navigator-base-font-size, 10px) on each component's :host and convert internal rem units to em, so sizing resolves against the component's own base. Consumers can override the var to rescale everything. - Normalize buttons with `font: inherit` (and reset the header/social icon wrappers), since form controls don't inherit font-size — otherwise em icons resolved against the browser's default button font-size. - Move menu-button text sizing off the flex container onto the label so the icon box isn't compounded. Verified in the demo: toggle/shortcut icons 24px, menu icon box 42px, labels 16px, close/sort/share icons ~20px — matching the upstream 10px-base scale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provider icons inside the open drawer weren't explicitly sized, so they filled the 42px icon box instead of matching the 24px minimized-rail shortcuts. Size the glyph within the menu-button icon box to --item-navigator-icon-width-- (the same var the rail uses) and drop the hardcoded 100% inline size on the demo's placeholder icon so CSS controls it. Drawer icons now render 24px, in line with the shortcuts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Animate the outer drawer open/close via transform + `inert` (instead of a display:none toggle, which can't transition), matching the inner panel. - Expose the minimized sidebar rail as `part="minimized-menu"` (upstream #27). - Close the menu sub-panel independently of the drawer: the slider emits `menuPanelClosed` and the navigator clears `openMenu` without closing the drawer, so a channel can be reopened afterward (fixes an openMenu desync). - Ease the reader's width/margin in shift mode so the slotted theater glides in sync with the drawer and settles smoothly on resize; overlay stays instant. - Add tests for the independent sub-panel close. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Enable menu-button labels in the demo (component ships icon-only by default). - Point demo data at the real `masterbookofamericanfolksong00shep` item so the viewable-file and share links resolve; keep PDF flags via file_source. - Bind the settings checkboxes with `.checked` so exiting fullscreen from the theater unchecks the Fullscreen box. - Add an "Animate" toggle that enables/disables all navigator animations via --item-navigator-animation-timing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Fullscreen: the base `#frame { height: inherit }` overrode `top/bottom: 0`
on the fixed element, so fullscreen was stuck at the wrapper height. Release
height (height: auto; min-height: 0) so the inset fills the viewport.
- Slider header: `selectedMenuAction` defaults to Lit's `nothing` sentinel,
which is truthy — so renderMenuHeader always applied `with-secondary-action`
and an empty action span. Compare against `nothing` explicitly.
- Add tests covering the previously-uncovered paths (item attribute converter,
shared-resize-observer wiring, menu-event handlers, shortcut rail, clipboard
fallback + copy-note timeout, sort comparators, viewable-files scroll
branches, slider focus restoration). All runtime component files are now at
100% statements/branches/functions/lines; 76 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…imation The minimized shortcut rail floats over the frame's left edge while the drawer is closed. Previously the reader reserved that width with a margin, so on a shift-mode open it animated from 42px to the drawer width while the drawer slid from 0 — leaving a shrinking gap so the theater appeared to trail the drawer. Reserve the rail's width with non-transitioned padding on the reader's content (only while closed) instead. The reader box now animates 0 -> drawer width in lockstep with the drawer (identical timing/easing), and the padding snaps away on open, so the theater stays flush with the drawer through the slide while still clearing the rail when closed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Theming: - Merge subpanel/separator/share borders into --item-navigator-border-color - Collapse all text onto --item-navigator-text-color - Merge icon knobs into --item-navigator-icon-color / -active-color / -inactive-color - Rename --item-navigator-textarea-bg -> --item-navigator-share-embed-bg - Drop no-op --item-navigator-share-icon-bg; remove dead loader fill=#333 Demo: - Styles panel shows the CSS var each control sets, plus a Reset button - List all theming/layout toggles; empty apply reverts to component defaults - Realistic archive.org cat items; clicking a viewable file swaps the theater iframe Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| d="m9 0c4.9705627 0 9 4.02943725 9 9 0 4.9705627-4.0294373 9-9 9-4.97056275 0-9-4.0294373-9-9 0-4.97056275 4.02943725-9 9-9zm1.6976167 5.28352881c-.365258-.3556459-.9328083-.37581056-1.32099801-.06558269l-.09308988.0844372-3 3.08108108-.08194436.09533317c-.27484337.36339327-.26799482.87009349.01656959 1.22592581l.084491.09308363 3 2.91891889.09533796.0818904c.3633964.2746544.8699472.2677153 1.2256839-.0167901l.093059-.0844712.0818904-.095338c.2746544-.3633964.2677153-.8699472-.0167901-1.2256839l-.0844712-.093059-2.283355-2.2222741 2.3024712-2.36338332.0819252-.09530804c.2997677-.39632298.2644782-.96313393-.1007797-1.31877983z" | ||
| fill-rule="evenodd" | ||
| /> | ||
| </svg> |
There was a problem hiding this comment.
Can we move these into standalone svg files so we're not packaging them as part of the JS bundle?
There was a problem hiding this comment.
Done — all 13 glyphs are standalone .svg files now (~18KB of path data out of the JS bundle). Verified none of the path strings remain in the emitted dist JS.
ia-item-navigator/icons/ellipses.svg
ia-item-navigator/icons/collapse-sidebar.svg
ia-item-navigator/menus/icons/share.svg
ia-item-navigator/menus/icons/twitter.svg
ia-item-navigator/menus/icons/facebook.svg
ia-item-navigator/menus/icons/tumblr.svg
ia-item-navigator/menus/icons/pinterest.svg
ia-item-navigator/menus/icons/email.svg
ia-item-navigator/menus/icons/link.svg
ia-item-navigator/menus/icons/viewable-files.svg
ia-item-navigator/menus/icons/sort-asc.svg
ia-item-navigator/menus/icons/sort-desc.svg
ia-item-navigator/menus/icons/sort-neutral.svg
One wrinkle worth flagging, since it drove the approach: these icons are recolored via CSS (--item-navigator-icon-color, plus active/inactive states on the menu buttons). <img src> — the pattern ia-combo-box and ia-dropdown-search-bar use — can't be recolored, so it would have dropped that theming.
So they render through a small maskedIcon() helper using mask-image + background-color: the mask supplies the shape, CSS supplies the paint. Same .svg-import mechanism as the other components, still themeable. Host CSS moved from fill to background-color, and the original .fill-color rules are kept alongside it so any inline-<svg> icon a consumer passes in (bookreader's bookmarks/search/downloads providers) keeps theming with no change on their side.
The loader's book/ring is the one exception and stays inline — a mask can't animate the spinning sub-path.
Also worth knowing if you hit this elsewhere: the url() has to be quoted. Bundlers inline small SVGs as data: URIs whose markup contains apostrophes, and an unquoted url() silently drops the whole mask-image declaration — the glyph then paints as a solid block.
| computer. | ||
| </p> | ||
| <a href=${this.downloadUrl}>Show all files</a> | ||
| </section> |
There was a problem hiding this comment.
We do this in offshoot here... how does this work with offshoot's? https://git.archive.org/www/offshoot/-/blob/main/src/scenes/details-page/theater/ia-theater-unavailable.ts#L71
There was a problem hiding this comment.
we can remove. this is legacy from porting, currently live in petabox
| /** Handler invoked by a shared resize observer when its target resizes. */ | ||
| export interface SharedResizeObserverResizeHandlerInterface { | ||
| handleResize(entry: ResizeObserverEntry): void; | ||
| } |
There was a problem hiding this comment.
Can we import this from @internetarchive/shared-resize-observer instead of re-creating it?
| * `HTMLElement` captures that contract without depending on | ||
| * `@internetarchive/modal-manager`. | ||
| */ | ||
| export type ModalManagerInterface = HTMLElement; |
There was a problem hiding this comment.
What happens when we use the actual ModalManager?
There was a problem hiding this comment.
Nothing happens — which is why I've removed it rather than keeping the local type.
The navigator accepted modal and never read it. Nothing downstream read it back off the navigator either: in bookreader the menu providers get the modal manager directly through baseProviderConfig (ia-bookreader.js:346-357), a path that doesn't go through the navigator at all — that's how BookmarksProvider ends up calling this.modal.showModal(). Offshoot never passed one.
Worth noting it's vestigial upstream too: iaux-item-navigator has exactly two modal mentions, the import and @property() modal?: ModalManager — it never forwards or calls it. So the port wasn't dropping behavior; there wasn't any.
ModalManagerInterface is gone with it, so service-interfaces now describes only the resize observer, which the navigator does genuinely use.
Consumers still binding .modal are unaffected — setting an unknown property on a Lit element is a no-op, and their providers keep their own reference. Confirmed in the demo: assigning .modal after the removal doesn't throw and the navigator renders and operates normally.
|
|
||
| /** Icon for the viewable-files menu shortcut / menu entry. */ | ||
| export const viewableFilesIcon = html` | ||
| <svg |
There was a problem hiding this comment.
Same comment... can we externalize this so it doesn't inflate the bundle size?
There was a problem hiding this comment.
Done — this one is menus/icons/viewable-files.svg now, alongside the other twelve. Same maskedIcon() treatment as the rest (details in the icons.ts thread): mask supplies the shape, background-color supplies the paint, so it still follows --item-navigator-icon-color.
Worth noting for consumers: this module's viewableFilesIcon export keeps its name and its TemplateResult type — it just renders a masked <span> now instead of an inline <svg>. bookreader and offshoot both import it, and neither needs a change unless they target it with their own svg/path CSS.
| * matching the current `subPrefix`. Populated by the host via `fileList` | ||
| * (typically re-ordered by an `ia-sort-files-button`). | ||
| */ | ||
| @customElement('ia-viewable-files-panel') |
There was a problem hiding this comment.
Looking at a lot of these custom elements, I think we should somehow scope their names to the element they're a part of since they're going to exist in the global namespace, ie ia-itemnav-viewable-files-panel
There was a problem hiding this comment.
Done — sub-elements are now scoped under ia-itemnav-* (tags, files and classes). Files changed:
Renamed
ia-itemnav-loader.ts → ia-itemnav-loading-view.ts
ia-itemnav-loader.test.ts → ia-itemnav-loading-view.test.ts
ia-menu-slider.ts → ia-itemnav-menu-slider.ts
ia-menu-slider.test.ts → ia-itemnav-menu-slider.test.ts
ia-menu-button.ts → ia-itemnav-menu-button.ts
ia-menu-button.test.ts → ia-itemnav-menu-button.test.ts
ia-no-theater-available.ts → ia-itemnav-no-theater-available.ts
ia-no-theater-available.test.ts → ia-itemnav-no-theater-available.test.ts
menus/ia-share-panel.ts → menus/ia-itemnav-share-panel.ts
menus/ia-share-panel.test.ts → menus/ia-itemnav-share-panel.test.ts
menus/ia-sort-files-button.ts → menus/ia-itemnav-sort-files-button.ts
menus/ia-sort-files-button.test.ts → menus/ia-itemnav-sort-files-button.test.ts
menus/ia-viewable-files-panel.ts → menus/ia-itemnav-viewable-files-panel.ts
menus/ia-viewable-files-panel.test.ts → menus/ia-itemnav-viewable-files-panel.test.ts
Added
src/elements/ia-item-navigator/element-names.test.ts
Modified
README.md
src/elements/index.ts
src/elements/ia-item-navigator/ia-item-navigator.ts
src/elements/ia-item-navigator/ia-item-navigator.test.ts
src/elements/ia-item-navigator/ia-item-navigator-story.ts
ia-item-navigator itself keeps its name. The loader became ia-itemnav-loading-view rather than ia-itemnav-loader because @internetarchive/ia-item-navigator already registers that exact name — along with ia-menu-slider and ia-no-theater-available, so those three would have thrown on double registration while consumers still load both packages.
| inline: 'nearest', | ||
| }); | ||
| } | ||
| }, 350); |
There was a problem hiding this comment.
What is the intended behavior here? This feels code smelly to put an arbitrary 350ms timeout on this scroll. Can we be more deterministic about this?
| } | ||
|
|
||
| render(): TemplateResult { | ||
| return html`${this.fileList.length ? this.fileListTemplate : nothing}`; |
There was a problem hiding this comment.
Do you need to wrap this in html since you're just returning the template or nothing?
There was a problem hiding this comment.
No — good catch, it was only there to satisfy the TemplateResult return type. Widening the signature lets the branch return as-is:
render(): TemplateResult | typeof nothing {
return this.fileList.length ? this.fileListTemplate : nothing;
}Existing coverage already asserts the empty case renders no ul, and I confirmed in the demo that the list still renders populated (7 items), disappears when fileList is emptied, and comes back on repopulate.
| * The contract a theater host implements to feed menu providers and shortcuts | ||
| * to the item navigator. | ||
| */ | ||
| export interface CustomTheaterInterface extends LitElement { |
There was a problem hiding this comment.
Based on the comment regarding "providers and shortcuts", this interface seems to be doing a lot more than that... passing itemMD, baseHost, observer, modal, etc. Can we scope this more concretely to those behaviors?
Also, how does this interact with offshoot's <ia-theater>?
| * A tiny adapter that satisfies the navigator's `SharedResizeObserverInterface` | ||
| * using a single native `ResizeObserver`, so the demo can show the responsive | ||
| * overlay/shift behavior without depending on | ||
| * `@internetarchive/shared-resize-observer`. |
There was a problem hiding this comment.
Why not just import the shared resize observer?
|
Can we scope the name of all of the elements, ie |
| ModalManagerInterface, | ||
| SharedResizeObserverInterface, | ||
| SharedResizeObserverResizeHandlerInterface, | ||
| } from './interfaces/service-interfaces'; |
There was a problem hiding this comment.
What happens when we use the actual Modal Manager and Shared Resize Observer?
There was a problem hiding this comment.
Two different answers, since the navigator treats them very differently.
Modal manager — nothing happens, so I've removed the property (details in the service-interfaces.ts thread). The navigator never read it, and nothing downstream reads it back: bookreader's providers get the modal directly via baseProviderConfig, bypassing the navigator entirely. It was unused upstream too.
Shared resize observer — a real instance drops straight in, because this one is genuinely load-bearing. The navigator implements the handler interface and uses it for real behavior:
- observes
#frameand flips between overlay and shift at the 600px breakpoint - observes the
headerslot and re-renders when its height changes - removes both on disconnect and when the observer property is swapped
The local interface is just the structural subset it calls — addObserver / removeObserver with { target, handler } — which a real SharedResizeObserver already satisfies. bookreader passes one today (new SharedResizeObserver() at ia-bookreader.js:129) and it works unchanged.
Happy to import the type from @internetarchive/shared-resize-observer instead of declaring it locally, per your other comment — that's a type-only import so it stays out of the runtime dependency tree either way.
The 13 glyphs moved from inline Lit templates to standalone .svg files, taking ~18KB of path data out of the JS bundle. They render through a shared maskedIcon() helper using CSS mask-image rather than <img>, so they stay recolorable by the --item-navigator-icon-color knobs; <img> would have dropped that theming. - shareIcon/viewableFilesIcon keep their TemplateResult export shape, now rendering a masked <span> instead of inline <svg> - Host theming switches to background-color, but the original .fill-color rules stay so consumer-supplied inline-svg icons still theme without changes - The loader's book/ring stays inline; a mask can't animate its spinning sub-path - Quote the mask url(): bundlers inline small SVGs as data URIs containing apostrophes, which an unquoted url() silently rejects - Demo gains a Randomize colors button to spot any glyph that fails to pick up its color knob Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Randomizing each color knob on its own could land dark text on a dark panel, so the control couldn't be trusted to show whether an element follows its color knob. It now swaps in one coordinated theme at a time — Midnight, Forest, Plum, Solarized or High Contrast — each checked against every foreground/background pair the navigator actually renders: WCAG AA throughout, AAA for text. Recoloring the whole component at once also makes a stray element obvious. Stories opt in by passing palettes alongside their style settings, so the other six demos keep the existing per-input behavior. The button now hides where there is nothing to recolor. Also fixes a latent bug that left the panel unable to re-render at all: the range readout and reset wrote to <output> with textContent, ejecting Lit's markers from a node it renders. The readout is now driven by reactive state instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The style-variable labels, revert control and randomizer went into the shared settings panel, so they appeared on every component's demo even though this work only concerns the item navigator. Stories now opt in: palettes bring the randomizer, revertable brings revert, and showCssVariables shows each control's custom property. Only the item navigator opts in, so the other six demos render exactly as before. Randomizing without palettes assigned each color on its own, which could pair dark text with a dark panel; with the control now limited to stories that define themes, that path is gone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Custom elements share one global registry, and names like ia-menu-slider or ia-share-panel are generic enough that anything on the page might want them. Three were worse than generic: ia-menu-slider, ia-itemnav-loader and ia-no-theater-available are registered verbatim by @internetarchive/ia-item-navigator, which bookreader and offshoot still load. Since the barrel re-exports this component, importing anything from elements would have registered those names a second time and thrown at module evaluation. The navigator's parts now carry its prefix, with files and classes following the tags. The loader becomes ia-itemnav-loading-view rather than ia-itemnav-loader, since the prefixed name is the one upstream already owns. The navigator itself keeps its name. Each element now declares its tag in HTMLElementTagNameMap, so querySelector is typed and a mistyped tag in a template fails the build instead of silently rendering an inert element. A test asserts the new names are registered and the old ones are left free, so a half-finished rename cannot pass quietly. Doing this before release costs consumers nothing: they reference the upstream tags today and rewrite those either way when they migrate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
render() wrapped its result in an html template purely to satisfy the TemplateResult return type; widening the type to include nothing lets it return the branch as-is, without the extra template. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The navigator accepted a modal property and never read it. Nothing downstream read it back either: in bookreader the menu providers get the modal manager directly through baseProviderConfig, a path that does not go through the navigator, and offshoot never passed one. The property was vestigial upstream too — iaux-item-navigator declares it and likewise never uses it. Removing it also retires the local ModalManagerInterface, leaving service-interfaces to describe only the resize observer, which the navigator genuinely uses. Consumers still binding .modal are unaffected: setting an unknown property on a Lit element is a no-op, and their providers keep their own reference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@jbuckner thanks for the review — pushed a batch addressing most of it. Replies are on the individual threads; summary here. AddressedExternalized the SVGs ( Scoped the element names ( Each element now declares its tag in Dropped the modal manager ( Removed the redundant Still open
Consumer impact from the renames is tracked in WEBDEV-8856 (bookreader) and WEBDEV-8857 (offshoot), including the ordering constraint that bookreader has to land first — offshoot reaches into its shadow root for the navigator tag.
|
WEBDEV-8458 Migrate item-navigator into elements
Ports
@internetarchive/iaux-item-navigatorinto the elements repo as aproduction component (
ia-item-navigator), re-styled to the elements CSSconventions, with a live demo and full test coverage.
<ia-item-navigator>, then expand Import, Usage & Settings for the live toggles/theming controls.main(for comparison): https://internetarchive.github.io/elements/What's included
The full upstream package (16 source files) — the navigator shell and both bundled menus:
ia-item-navigator— fullscreen-capable frame:header/mainslots, a collapsible drawer driven by amenuContentsprovider array, a minimizedmenuShortcutsrail, responsive overlay/shift, loader, and no-theater placeholder.ia-menu-slider,ia-menu-button,ia-itemnav-loader,ia-no-theater-available— the shell's internal pieces.ia-viewable-files-panel+ia-sort-files-button— viewable-files menu (scrollable volume list, active-file highlight, PDF flagging, tri-state title sort).ia-share-panel— social share links + copyable iframe/bbcode embed snippets.Lives at
src/elements/ia-item-navigator/; exported fromsrc/elements/index.ts.Key decisions
ia-*/IA*convention across all tags and classes (upstream mixediaux-*, un-prefixed, and Lit-2 casing).modal-manager/shared-resize-observerwere type-only upstream → replaced with local interfaces; all 11 icons inlined as Lit templates. Only new runtime dep is@internetarchive/metadata-service(genuinely used in theitemconverter).--item-navigator-*with private--x--aliases + inline defaults. Consolidated onto a small palette:--item-navigator-border-color,--item-navigator-text-color,--item-navigator-icon-color/-active-color/-inactive-color,--item-navigator-share-embed-bg, plus surface/animation knobs.--item-navigator-base-font-size) withemunits, reproducing petabox's scale without forcing a 10px root on the rest of the demo.Notable fixes vs. source
@click=${this.toggleMenu}, settingmenuOpenedto aPointerEvent(only "worked" because truthy) → now stays boolean.execCommand('copy')→ async Clipboard API (with fallback); sort works on a copy instead of mutating the reactive prop; dropped a dead never-rendered method.Demo
Self-contained
ia-item-navigatorstory (Production-Ready): realistic archive.org items (images / PDFs / video) where clicking a viewable file swaps the theater iframe; slotted header + theater; share panel + populated shortcut rail; and a Styles panel that lists every theming/layout toggle, shows the CSS var each control sets, and has a Reset.Testing
npm run build,npm run lint, and the madge circular check all pass.Follow-up (out of scope, tracked)
Shared dark-surface palette tokens for the near-blacks / border / scrim that have no house equivalent today — WEBDEV-8796 / #66.
QA Steps
Setup: open the live demo → Production-Ready →
<ia-item-navigator>, expand Import, Usage & Settings.::part(minimized-menu)targets the rail.npm test(76 navigator tests),npm run build,npm run lintpass.