JavaScript

Twelve components in 6 kB gzipped, written in TypeScript and tested. No dependency, no setup: the markup is enough.

Usage

Setup

One deferred script. It starts by itself once the page is parsed: the markup is enough.


                        <script src="js/iroom.min.js" defer></script>
                    

To pass options, keep it from starting with data-manual and start it yourself:


                        <script src="js/iroom.min.js" data-manual defer></script>
                        <script type="module">
                            Iroom.start({ messages: { close: 'Fermer' } });
                        </script>
                    

With a bundler, import the ES module: it starts nothing and touches no DOM on import, so it also loads on a server.


                        import { start } from 'iroom';

                        start();
                    

Usage

Lifecycle

One listener per event type, on the document, for every component: the elements added later work too.

Iroom.start(options?)adds the listeners, sets up the page and watches the markup inserted later (a MutationObserver). A second call does nothing.
Iroom.stop()removes every listener (one AbortController), the observers and the elements created by the components
Iroom.init(root?)sets up a part of the page by hand, when observe is off; safe to call twice
Iroom.isStarted(), Iroom.versionthe state and the version of the framework

Usage

Options

OptionDefaultUse
observetruesets up the markup inserted after the start
scrolledFar600pixels of scroll before data-scrolled="far" (the back to top button)
themeKey'iroom-theme'the localStorage key of the saved theme
messagesEnglishthe texts for the screen readers: imageViewer, close, previousImage, nextImage

The large breakpoint of the navbar is not an option: it is read from the --ir-breakpoint-l token of the CSS, so the two never disagree.

Usage

Events

Every change is announced by an event that bubbles. The cancelable ones can stop the change with preventDefault().

EventOnDetail
ir:dropdown-open cancelable, ir:dropdown-close.dropdowntoggle, menu
ir:navbar-toggle cancelablethe toggletoggle, menu, open
ir:tab-change cancelable.tabstab, panel, index
ir:lightbox-open cancelable, ir:lightbox-change, ir:lightbox-closethe link, the dialoglink, index
ir:filter-change cancelable[data-filter-group]value
ir:dismiss cancelablethe alert
ir:password-togglethe fieldinput, visible
ir:theme-change<html>theme, dark

                        document.addEventListener('ir:tab-change', (event) => {
                            if (formIsDirty) {
                                event.preventDefault();    // the tab stays
                            }
                        });
                    

Usage

Functions

To drive the components from your code.

openDropdown(toggle, focus?), closeDropdown(restoreFocus?)focus: 'first' or 'last' item
toggleNavbar(toggle, open?), closeNavbars()the menus of the navbars
selectTab(tab, focus?)a tab and its panel
openLightbox(link)a [data-lightbox] link and its gallery
applyFilter(group, value)the items of a [data-filter-group]
dismiss(element)removes an element after a fade
setTheme(theme, persist?), getTheme(), isDark()'light', 'dark' or 'auto'

Components

Overview

Twelve components. Their markup is on the components and CSS pages.

.dropdown-togglearia-expanded; a click outside, Esc, Tab or a choice closes; arrows, Home, End
.navbar-togglethe menu named by aria-controls; Esc, a link, a click outside or a large screen closes it
.tabsroles and ARIA attributes, arrows, Home, End
commandfor, closedbybrought to the browsers that lack them
[data-lightbox]a modal dialog, a gallery, arrows, swipe
[data-filter]filters with view transitions
[data-dismiss]removes its alert after a fade
[data-password-toggle]shows or hides a password
[data-theme-toggle]light or dark, saved
data-scrolled, [data-scrollspy]the scroll state, the current section
.parallaxthe progress of the visible sections, turned into a translate by the CSS

Components

Lightbox

A link to the large image: without JavaScript it opens the image, with it a modal dialog. The same data-lightbox makes a gallery.

Mountains at sunset A desk at night A city of twelve columns at dusk

← and → navigate, Esc or a click on the background closes; a click with Ctrl still opens the image in a new tab.

Components

Filters

Buttons with data-filter, items with data-category (several are allowed), in a data-filter-group.

web
print
video
web, print
video
print

Components

Theme

A data-theme-toggle button switches between light and dark, saves the choice and sets aria-pressed; .toggle-on and .toggle-off swap its content.

Components

Scroll and scrollspy

<html> gets data-scrolled="near" once the page scrolls, "far" after 600 pixels: the sticky navbar gets a shadow, the transparent one becomes solid, .back-to-top shows up.

In a data-scrollspy element, the link of the section crossing the reading line gets aria-current="true": the menu of this page. An IntersectionObserver does the work, nothing is measured on scroll.


                        <nav data-scrollspy>
                            <a href="#setup">Setup</a>
                            <a href="#events">Events</a>
                        </nav>

                        <a href="#top" class="back-to-top" aria-label="Back to top">…</a>
                    

Going further

Your own component

The components of the framework are built the same way: register yours, it gets the same lifecycle.


                        iroom.devinthehood.comgister({
                            name: 'copy',
                            // once per start: the signal removes the listener on stop()
                            listen({ signal }) {
                                document.addEventListener('click', (event) => {
                                    const button = event.target.closest('[data-copy]');
                                    if (button) navigator.clipboard.writeText(button.dataset.copy);
                                }, { signal });
                            },
                            // for the page, then for every inserted part: must be safe to call twice
                            setup(root) {
                                root.querySelectorAll('[data-copy]').forEach((button) => button.setAttribute('title', 'Copy'));
                            },
                        });
                    

Going further

Build, types and tests

Strict TypeScript in ts/, one file per component, bundled by esbuild.

js/iroom.jsthe script, readable, for the debugging
js/iroom.min.js and its source mapminified, 6 kB gzipped
js/iroom.esm.js and its source mapthe ES module, without side effect
js/types/the declarations, the events typed for addEventListener

                        npm run build:js    # type checking, then the files above
                        npm run check       # Biome (lint, format) and Vitest (the tests of every component)