UNPKG

fluent-svelte-extra

Version:

A faithful implementation of Microsoft's Fluent Design System in Svelte.

188 lines (187 loc) 7.34 kB
import { bubble, listen } from "svelte/internal"; import { tabbable } from "tabbable"; import { createFocusTrap } from "focus-trap"; export { default as ComboBoxItem } from "./ComboBox/ComboBoxItem.svelte"; export { default as FlyoutSurface } from "./Flyout/FlyoutSurface.svelte"; export { default as TooltipSurface } from "./Tooltip/TooltipSurface.svelte"; export { default as MenuFlyoutSurface } from "./MenuFlyout/MenuFlyoutSurface.svelte"; export { default as CalendarViewItem } from "./CalendarView/CalendarViewItem.svelte"; const storage = {}; export function setKey(key, value) { storage[key] = value; } export function getKey(key) { return storage[key]; } export function deleteKey(key) { delete storage[key]; } export function externalMouseEvents(node, options = { type: "click", stopPropagation: false }) { const { type, stopPropagation } = options; const handleEvent = (event) => { if (stopPropagation) event.stopPropagation(); if (node && !node.contains(event.target) && !event.defaultPrevented) { node.dispatchEvent(new CustomEvent(`outer${type}`, { detail: event })); } }; document.addEventListener(type, handleEvent, true); return { destroy() { document.removeEventListener(type, handleEvent, true); } }; } // Basic wrapper action around focus-trap export function focusTrap(node, options) { const trap = createFocusTrap(node, (options = { allowOutsideClick: true, ...options, fallbackFocus: node })); trap.activate(); return { destroy() { trap.deactivate(); } }; } // ID generator for handling WAI-ARIA related attributes export function uid(prefix) { return (prefix + String.fromCharCode(Math.floor(Math.random() * 26) + 97) + Math.random().toString(16).slice(2) + Date.now().toString(16).split(".")[0]); } // Controls the focus of a list of elements by using the arrow keys export function arrowNavigation(node, options = { preventTab: false, stopPropagation: false }) { const handleKeyDown = (event) => { const { key } = event; const { activeElement } = document; let tabOrder = tabbable(node); // if (directChildren) tabOrder = tabOrder.filter(child => child.parentElement === node); const activeIndex = tabOrder.indexOf(document.activeElement); if (tabOrder.length < 0) return; if (key === "ArrowUp" || key === "ArrowDown" || key === "Home" || key === "End" || (key === "Tab" && options.preventTab)) { event.preventDefault(); if (options.stopPropagation) event.stopPropagation(); } if (key === "ArrowUp") { if (tabOrder[0] === activeElement) { tabOrder[tabOrder.length - 1].focus(); } else if (tabOrder.includes(activeElement)) { tabOrder[activeIndex - 1].focus(); } } else if (key === "ArrowDown") { if (tabOrder[tabOrder.length - 1] === activeElement) { tabOrder[0].focus(); } else if (tabOrder.includes(activeElement)) { tabOrder[activeIndex + 1].focus(); } } else if (key === "Home") { tabOrder[0].focus(); } else if (key === "End") { tabOrder[tabOrder.length - 1].focus(); } }; node.addEventListener("keydown", handleKeyDown); return { destroy: () => node.removeEventListener("keydown", handleKeyDown) }; } // Returns a number representing the duration of a specified CSS custom property in ms export function getCSSDuration(property) { const duration = window.getComputedStyle(document.documentElement).getPropertyValue(property); return parseFloat(duration) * (/\ds$/.test(duration) ? 1000 : 1) || 0; } export function conditionalEvent(node, { condition, event, callback }) { if (condition) { node.addEventListener(event, callback); } return { destroy() { node.removeEventListener(event, callback); } }; } // Function for forwarding DOM events to the component's declaration // Adapted from rgossiaux/svelte-headlessui which is modified from hperrin/svelte-material-ui export function createEventForwarder(component, exclude = []) { // This is our pseudo $on function. It is defined on component mount. let $on; // This is a list of events bound before mount. let events = []; // Monkeypatch SvelteComponent.$on with our own forward-compatible version component.$on = (eventType, callback) => { let destructor = () => { }; if (exclude.includes(eventType)) { // Bail out of the event forwarding and run the normal Svelte $on() code const callbacks = component.$$.callbacks[eventType] || (component.$$.callbacks[eventType] = []); callbacks.push(callback); return () => { const index = callbacks.indexOf(callback); if (index !== -1) callbacks.splice(index, 1); }; } if ($on) { destructor = $on(eventType, callback); // The event was bound programmatically. } else { events.push([eventType, callback]); // The event was bound before mount by Svelte. } return () => destructor(); }; return (node) => { const destructors = []; const forwardDestructors = {}; const forward = (e) => bubble(component, e); // This function is responsible for listening and forwarding // all bound events. $on = (eventType, callback) => { let handler = callback; // DOM addEventListener options argument. let options = false; // Listen for the event directly, with the given options. const off = listen(node, eventType, handler, options); const destructor = () => { off(); const idx = destructors.indexOf(destructor); if (idx > -1) { destructors.splice(idx, 1); } }; destructors.push(destructor); // Forward the event from Svelte. if (!(eventType in forwardDestructors)) { forwardDestructors[eventType] = listen(node, eventType, forward); } return destructor; }; // Listen to all the events added before mount. for (const event of events) { $on(event[0], event[1]); } return { destroy: () => { // Remove all event listeners. for (const destructor of destructors) { destructor(); } // Remove all event forwarders. for (let entry of Object.entries(forwardDestructors)) { entry[1](); } } }; }; }