RZREDZ.UI

Trees

Pages under pages, a menu's items, folders: a list whose items open their own lists, each a link or a name, and moved by a handle.

Examples

tree on the list, tree-item on each item, its tree-row holding a tree-toggle - a --toggle button naming the item's tree-group in aria-controls, as on collapse - and the item's link. The groups close as hidden="until-found", so the browser's find in page still opens them. A button with command="--expand-all" or "--collapse-all" for the tree opens or closes every group that holds items.

<div class="hstack gap-8">
	<button class="btn btn-sm" type="button" commandfor="tree-pages-list" command="--expand-all"><i class="bi bi-arrows-expand" aria-hidden="true"></i>Expand all</button>
	<button class="btn btn-sm" type="button" commandfor="tree-pages-list" command="--collapse-all"><i class="bi bi-arrows-collapse" aria-hidden="true"></i>Collapse all</button>
</div>
<ul class="tree" id="tree-pages-list" aria-label="Pages">
	<li class="tree-item">
		<div class="tree-row">
			<button class="btn btn-sm btn-icon btn-text tree-toggle" type="button" command="--toggle" aria-controls="tree-pages-excursions" aria-expanded="true" aria-label="Excursions"><i class="bi bi-chevron-right" aria-hidden="true"></i></button>
			<a href="#tree-pages">Excursions</a>
		</div>
		<ul class="tree-group" id="tree-pages-excursions">
			<li class="tree-item"><div class="tree-row"><a href="#tree-pages">North Bay by speedboat</a></div></li>
			<li class="tree-item"><div class="tree-row"><a href="#tree-pages">Night market and street food</a></div></li>
		</ul>
	</li>
	<li class="tree-item">
		<div class="tree-row">
			<button class="btn btn-sm btn-icon btn-text tree-toggle" type="button" command="--toggle" aria-controls="tree-pages-islands" aria-expanded="false" aria-label="Islands"><i class="bi bi-chevron-right" aria-hidden="true"></i></button>
			<a href="#tree-pages">Islands</a>
		</div>
		<ul class="tree-group" id="tree-pages-islands" hidden="until-found">
			<li class="tree-item"><div class="tree-row"><a href="#tree-pages">Turtle Cove</a></div></li>
			<li class="tree-item"><div class="tree-row"><a href="#tree-pages">Moon Cove</a></div></li>
		</ul>
	</li>
	<li class="tree-item">
		<div class="tree-row">
			<button class="btn btn-sm btn-icon btn-text tree-toggle" type="button" command="--toggle" aria-controls="tree-pages-archive" aria-expanded="false" aria-label="Archive"><i class="bi bi-chevron-right" aria-hidden="true"></i></button>
			<a href="#tree-pages">Archive</a>
		</div>
		<ul class="tree-group" id="tree-pages-archive" hidden="until-found" data-lazy data-docs-lazy></ul>
	</li>
	<li class="tree-item"><div class="tree-row"><a href="#tree-pages">Contacts</a></div></li>
</ul>

Children from the server

A group with data-lazy and no items fires redz:expand on it when it first opens. A page that loads the items cancels it and answers with a promise: the toggle is busy until it settles, and a refusal closes the group again for another try. The Archive above answers after 400 ms.

document.addEventListener('redz:expand', (event) => {
	event.preventDefault();
	event.detail.respondWith(fetch('/pages?parent=' + event.target.dataset.parent)
		.then((answer) => answer.text())
		.then((items) => event.target.insertAdjacentHTML('beforeend', items)));
});

Moved by a handle

A tree with data-reorder moves its items as a list does - see reordering - and its menu of moves adds data-move="in", to the end of the group of the item above, and "out", after its parent; an item can go in where the item above has a tree-group, empty or not. data-parent names the hidden field of each item's parent, its value the item's key.

  • Home
    • Tours
      • Boats
      • Contacts
        <ul class="tree" id="tree-menu-list" aria-label="The site's menu" data-reorder="menu[]" data-parent="menu_parent">
        	<li class="tree-item" data-value="home">
        		<div class="tree-row">
        			<button class="btn btn-sm btn-icon btn-text drag-handle" type="button" commandfor="tree-menu-moves" command="toggle-popover" aria-haspopup="menu" aria-label="Move: Home"><i class="bi bi-grip-vertical" aria-hidden="true"></i></button>
        			<span>Home</span>
        		</div>
        		<ul class="tree-group" id="tree-menu-home"></ul>
        	</li>
        	<li class="tree-item" data-value="tours">
        		<div class="tree-row">
        			<button class="btn btn-sm btn-icon btn-text drag-handle" type="button" commandfor="tree-menu-moves" command="toggle-popover" aria-haspopup="menu" aria-label="Move: Tours"><i class="bi bi-grip-vertical" aria-hidden="true"></i></button>
        			<span>Tours</span>
        		</div>
        		<ul class="tree-group" id="tree-menu-tours">
        			<li class="tree-item" data-value="boats">
        				<div class="tree-row">
        					<button class="btn btn-sm btn-icon btn-text drag-handle" type="button" commandfor="tree-menu-moves" command="toggle-popover" aria-haspopup="menu" aria-label="Move: Boats"><i class="bi bi-grip-vertical" aria-hidden="true"></i></button>
        					<span>Boats</span>
        				</div>
        				<ul class="tree-group" id="tree-menu-boats"></ul>
        			</li>
        		</ul>
        	</li>
        	<li class="tree-item" data-value="contacts">
        		<div class="tree-row">
        			<button class="btn btn-sm btn-icon btn-text drag-handle" type="button" commandfor="tree-menu-moves" command="toggle-popover" aria-haspopup="menu" aria-label="Move: Contacts"><i class="bi bi-grip-vertical" aria-hidden="true"></i></button>
        			<span>Contacts</span>
        		</div>
        		<ul class="tree-group" id="tree-menu-contacts"></ul>
        	</li>
        </ul>
        <div class="menu" id="tree-menu-moves" popover role="menu" aria-label="Move">
        	<button class="menu-item" type="button" role="menuitem" data-move="up"><i class="bi bi-arrow-up" aria-hidden="true"></i>Move up</button>
        	<button class="menu-item" type="button" role="menuitem" data-move="down"><i class="bi bi-arrow-down" aria-hidden="true"></i>Move down</button>
        	<button class="menu-item" type="button" role="menuitem" data-move="in"><i class="bi bi-arrow-return-right flip-rtl" aria-hidden="true"></i>Move into the item above</button>
        	<button class="menu-item" type="button" role="menuitem" data-move="out"><i class="bi bi-arrow-bar-left flip-rtl" aria-hidden="true"></i>Move out</button>
        </div>

        The current page, actions and checks

        A row whose link has aria-current - the page open now - is tinted and its link bold. tree-actions at the end of a row holds its buttons: quiet until the row is hovered, holds the focus or has a menu open on a pointer screen, always shown on a touch screen. A check in a row posts by its own name, for a batch over the chosen pages. A search with data-filter keeps the parents of a match.

        <input class="form-control" type="search" data-filter="tree-current-list" aria-label="Find a page" placeholder="Find a page" autocomplete="off">
        <ul class="tree" id="tree-current-list" aria-label="Pages to publish">
        	<li class="tree-item">
        		<div class="tree-row">
        			<input class="check" type="checkbox" name="pages[]" value="excursions" aria-label="Choose: Excursions">
        			<a href="#tree-current">Excursions</a>
        			<span class="tree-actions"><button class="btn btn-xs btn-icon btn-text" type="button" aria-label="Add a page under Excursions"><i class="bi bi-plus-lg" aria-hidden="true"></i></button></span>
        		</div>
        		<ul class="tree-group">
        			<li class="tree-item">
        				<div class="tree-row">
        					<input class="check" type="checkbox" name="pages[]" value="north-bay" aria-label="Choose: North Bay by speedboat" checked>
        					<a href="#tree-current" aria-current="page">North Bay by speedboat</a>
        					<span class="tree-actions"><button class="btn btn-xs btn-icon btn-text" type="button" aria-label="Add a page under North Bay by speedboat"><i class="bi bi-plus-lg" aria-hidden="true"></i></button></span>
        				</div>
        			</li>
        			<li class="tree-item">
        				<div class="tree-row">
        					<input class="check" type="checkbox" name="pages[]" value="night-market" aria-label="Choose: Night market and street food">
        					<a href="#tree-current">Night market and street food</a>
        					<span class="tree-actions"><button class="btn btn-xs btn-icon btn-text" type="button" aria-label="Add a page under Night market and street food"><i class="bi bi-plus-lg" aria-hidden="true"></i></button></span>
        				</div>
        			</li>
        		</ul>
        	</li>
        </ul>

        Keyboard and accessibility

        • The tree is a list of lists with buttons and links - the disclosure pattern: Tab goes from one control to the next, Enter or Space opens or closes a group, and the toggle says it in aria-expanded. It is not the ARIA tree widget, which the APG itself keeps for choosing, not for links.
        • A toggle is named after its item; its chevron points along the line of reading, mirrored right to left.
        • The moves are a menu on the handle, so a keyboard, a screen reader and a single tap move an item as a drag does; after a move the focus stays on the moved item's handle and its new place is said politely.

        Tokens

        From css/components/tree.css, which declares no custom property of its own.

        It reads the theme's --redz-space-12, --redz-space-8, --redz-border, --redz-space-4, --redz-control-height, --redz-control-height-sm, --redz-radius-6, --redz-primary-subtle, --redz-text, --redz-motion, --redz-ease.