UNPKG

@bentoproject/accordion

Version:

Displays content sections that can be collapsed and expanded.

709 lines (694 loc) 20 kB
// extensions/amp-accordion/1.0/component.jss.js var $sectionChild = "section-child-00fce3c"; var $header = "header-00fce3c"; var $contentHidden = "content-hidden-00fce3c"; // src/core/data-structures/promise.js var resolved; function resolvedPromise() { if (resolved) { return resolved; } resolved = Promise.resolve(void 0); return resolved; } // node_modules/obj-str/dist/obj-str.mjs function obj_str_default(obj) { var k, cls = ""; for (k in obj) { if (obj[k]) { cls && (cls += " "); cls += k; } } return cls; } // src/core/data-structures/id-generator.js function sequentialIdGenerator() { let counter = 0; return () => String(++counter); } function randomIdGenerator(maxValue) { return () => String(Math.floor(Math.random() * maxValue)); } // src/core/types/object/index.js var { hasOwnProperty: hasOwn_, toString: toString_ } = Object.prototype; function map(opt_initial) { const obj = Object.create(null); if (opt_initial) { Object.assign(obj, opt_initial); } return obj; } function omit(o, props) { return Object.keys(o).reduce((acc, key) => { if (!props.includes(key)) { acc[key] = o[key]; } return acc; }, {}); } // src/preact/index.js import { cloneElement, createContext, createElement, createRef } from "preact"; import { hydrate, render } from "preact"; import { useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; function createElement2(unusedType, unusedProps, var_args) { return createElement.apply(void 0, arguments); } function createContext2(value) { return createContext(value, void 0); } function useState2(initial) { return useState(initial); } function useRef2(initial) { return useRef(initial); } function useEffect2(effect, opt_deps) { useEffect(effect, opt_deps); } function useLayoutEffect2(effect, opt_deps) { useLayoutEffect(effect, opt_deps); } function useContext2(context2) { return useContext(context2); } function useMemo2(cb, opt_deps) { return useMemo(cb, opt_deps); } function useCallback2(cb, opt_deps) { return useCallback(cb, opt_deps); } function useImperativeHandle2(ref, create, opt_deps) { return useImperativeHandle(ref, create, opt_deps); } // src/preact/compat.js import { options, toChildArray } from "preact"; // src/core/mode/prod.js function isProd() { return false; } // src/preact/compat.js var _excluded = ["ref"]; function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } var REACT_FORWARD_SYMBOL = typeof Symbol !== "undefined" && (Symbol.for == null ? void 0 : Symbol.for("react.forward_ref")) || 3911; var oldDiff = options["__b"]; options["__b"] = newDiff; function newDiff(vnode) { var _vnode$type; if ((_vnode$type = vnode["type"]) != null && _vnode$type.forwardRef_ && vnode["ref"]) { vnode["props"]["ref"] = vnode["ref"]; vnode["ref"] = null; } oldDiff == null ? void 0 : oldDiff(vnode); } function forwardRef(Component) { function Forward(props) { const { ref } = props, clone = _objectWithoutPropertiesLoose(props, _excluded); return Component(clone, ref); } Forward.$$typeof = REACT_FORWARD_SYMBOL; Forward.render = Forward; Forward.prototype.isReactComponent = true; Forward.forwardRef_ = true; if (!isProd()) { Forward.displayName = `ForwardRef(${Component.displayName || Component.name})`; } return Forward; } // src/core/constants/loading-instructions.js var Loading_Enum = { AUTO: "auto", LAZY: "lazy", EAGER: "eager", UNLOAD: "unload" }; var ORDER = [Loading_Enum.AUTO, Loading_Enum.LAZY, Loading_Enum.EAGER, Loading_Enum.UNLOAD]; var MAP = { [Loading_Enum.AUTO]: 0, [Loading_Enum.LAZY]: 1, [Loading_Enum.EAGER]: 2, [Loading_Enum.UNLOAD]: 3 }; function reducer(v1, v2) { const ordinal1 = MAP[v1] || 0; const ordinal2 = MAP[v2] || 0; const ordinal = Math.max(ordinal1, ordinal2); return ORDER[ordinal]; } // src/preact/context.js var context; function getAmpContext() { return context || (context = createContext2({ renderable: true, playable: true, loading: Loading_Enum.AUTO })); } function WithAmpContext({ children, loading: loadingProp = "auto", notify: notifyProp, playable: playableProp = true, renderable: renderableProp = true }) { const parent = useAmpContext(); const renderable = renderableProp && parent.renderable; const playable = renderable && playableProp && parent.playable; const loading = reducer(renderable ? Loading_Enum.AUTO : Loading_Enum.LAZY, reducer(loadingProp, parent.loading)); const notify = notifyProp || parent.notify; const current = useMemo2(() => ({ renderable, playable, loading, notify }), [renderable, playable, loading, notify]); const AmpContext = getAmpContext(); return createElement2(AmpContext.Provider, { children, value: current }); } function useAmpContext() { const AmpContext = getAmpContext(); return useContext2(AmpContext); } // src/preact/utils.js function propName(name) { return name; } // src/core/dom/style.js var propertyNameCache; var vendorPrefixes = ["Webkit", "webkit", "Moz", "moz", "ms", "O", "o"]; function camelCaseToTitleCase(camelCase) { return camelCase.charAt(0).toUpperCase() + camelCase.slice(1); } function getVendorJsPropertyName_(style, titleCase) { for (let i = 0; i < vendorPrefixes.length; i++) { const propertyName = vendorPrefixes[i] + titleCase; if (style[propertyName] !== void 0) { return propertyName; } } return ""; } function getVendorJsPropertyName(style, camelCase, opt_bypassCache) { if (isVar(camelCase)) { return camelCase; } if (!propertyNameCache) { propertyNameCache = map(); } let propertyName = propertyNameCache[camelCase]; if (!propertyName || opt_bypassCache) { propertyName = camelCase; if (style[camelCase] === void 0) { const titleCase = camelCaseToTitleCase(camelCase); const prefixedPropertyName = getVendorJsPropertyName_(style, titleCase); if (style[prefixedPropertyName] !== void 0) { propertyName = prefixedPropertyName; } } if (!opt_bypassCache) { propertyNameCache[camelCase] = propertyName; } } return propertyName; } function setStyle(element, property, value, opt_units, opt_bypassCache) { const propertyName = getVendorJsPropertyName(element.style, property, opt_bypassCache); if (!propertyName) { return; } const styleValue = opt_units ? value + opt_units : value; if (isVar(propertyName)) { element.style.setProperty(propertyName, styleValue); } else { element.style[propertyName] = styleValue; } } function getStyle(element, property, opt_bypassCache) { const propertyName = getVendorJsPropertyName(element.style, property, opt_bypassCache); if (!propertyName) { return void 0; } if (isVar(propertyName)) { return element.style.getPropertyValue(propertyName); } return element.style[propertyName]; } function setStyles(element, styles) { for (const k in styles) { setStyle(element, k, styles[k]); } } function isVar(property) { return property.startsWith("--"); } // extensions/amp-accordion/1.0/animations.js var MAX_TRANSITION_DURATION = 500; var MIN_TRANSITION_DURATION = 200; var EXPAND_CURVE = "cubic-bezier(0.47, 0, 0.745, 0.715)"; var COLLAPSE_CURVE = "cubic-bezier(0.39, 0.575, 0.565, 1)"; function animateExpand(content) { return animate(content, () => { const oldHeight = getStyle(content, "height"); const oldOpacity = getStyle(content, "opacity"); const oldOverflowY = getStyle(content, "overflowY"); setStyles(content, { height: 0, opacity: 0, overflowY: "auto" }); const targetHeight = content.scrollHeight; setStyles(content, { height: oldHeight, opacity: oldOpacity, overflowY: oldOverflowY }); const duration = getTransitionDuration(targetHeight); return content.animate([{ height: 0, opacity: 0, overflowY: "hidden" }, { height: targetHeight + "px", opacity: 1, overflowY: "hidden" }], { easing: EXPAND_CURVE, duration }); }); } function animateCollapse(content) { return animate(content, () => { const startHeight = content.offsetHeight; const duration = getTransitionDuration(startHeight); return content.animate([{ height: startHeight + "px", opacity: 1, overflowY: "hidden" }, { height: "0", opacity: 0, overflowY: "hidden" }], { easing: COLLAPSE_CURVE, duration }); }); } function animate(element, prepare, cleanup = void 0) { element.classList.add("i-amphtml-animating"); let player = prepare(); player.onfinish = player.oncancel = () => { player = null; if (cleanup) { cleanup(); } element.classList.remove("i-amphtml-animating"); }; return () => { if (player) { player.cancel(); } }; } function getTransitionDuration(dy) { const maxY = window.innerHeight; const distanceAdjustedDuration = Math.abs(dy) / maxY * MAX_TRANSITION_DURATION; return Math.min(Math.max(distanceAdjustedDuration, MIN_TRANSITION_DURATION), MAX_TRANSITION_DURATION); } // extensions/amp-accordion/1.0/component.js var _excluded2 = ["animate", "as", "children", "expandSingleSection", "id"]; var _excluded22 = ["animate", "as", "children", "expanded", "id", "onExpandStateChange"]; function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== void 0) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _extends() { _extends = Object.assign || function(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose2(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } var AccordionContext = createContext2({}); var SectionContext = createContext2({}); var EMPTY_EXPANDED_MAP = {}; var EMPTY_EVENT_MAP = {}; var generateSectionId = sequentialIdGenerator(); var generateRandomId = randomIdGenerator(1e5); function BentoAccordionWithRef(_ref, ref) { let { animate: animate2 = false, as: Comp = "section", children, expandSingleSection = false, id } = _ref, rest = _objectWithoutPropertiesLoose2(_ref, _excluded2); const [expandedMap, setExpandedMap] = useState2(EMPTY_EXPANDED_MAP); const eventMapRef = useRef2(EMPTY_EVENT_MAP); const [randomPrefix] = useState2(generateRandomId); const prefix = id || `a${randomPrefix}`; useEffect2(() => { if (!expandSingleSection) { return; } setExpandedMap((expandedMap2) => { const newExpandedMap = {}; let expanded = 0; for (const k in expandedMap2) { newExpandedMap[k] = expandedMap2[k] && expanded++ == 0; } return newExpandedMap; }); }, [expandSingleSection]); const registerSection = useCallback2((id2, defaultExpanded, { current: onExpandStateChange }) => { setExpandedMap((expandedMap2) => { return setExpanded(id2, defaultExpanded, expandedMap2, expandSingleSection); }); eventMapRef.current = _extends({}, eventMapRef.current, { [id2]: onExpandStateChange }); return () => { setExpandedMap((expandedMap2) => omit(expandedMap2, id2)); eventMapRef.current = omit(eventMapRef.current, id2); }; }, [expandSingleSection]); const toggleExpanded = useCallback2((id2, opt_expand) => { setExpandedMap((expandedMap2) => { const newExpanded = opt_expand != null ? opt_expand : !expandedMap2[id2]; const newExpandedMap = setExpanded(id2, newExpanded, expandedMap2, expandSingleSection); resolvedPromise().then(() => { for (const k in expandedMap2) { const onExpandStateChange = eventMapRef.current[k]; if (onExpandStateChange && expandedMap2[k] != newExpandedMap[k]) { onExpandStateChange(newExpandedMap[k]); } } }); return newExpandedMap; }); }, [expandSingleSection]); const isExpanded = useCallback2((id2, defaultExpanded) => { var _expandedMap$id; return (_expandedMap$id = expandedMap[id2]) != null ? _expandedMap$id : defaultExpanded; }, [expandedMap]); const toggle = useCallback2((id2) => { if (id2) { if (id2 in expandedMap) { toggleExpanded(id2); } } else { if (!expandSingleSection) { for (const k in expandedMap) { toggleExpanded(k); } } } }, [expandedMap, toggleExpanded, expandSingleSection]); const expand = useCallback2((id2) => { if (id2) { if (!isExpanded(id2, true)) { toggleExpanded(id2); } } else { if (!expandSingleSection) { for (const k in expandedMap) { if (!isExpanded(k, true)) { toggleExpanded(k); } } } } }, [expandedMap, toggleExpanded, isExpanded, expandSingleSection]); const collapse = useCallback2((id2) => { if (id2) { if (isExpanded(id2, false)) { toggleExpanded(id2); } } else { for (const k in expandedMap) { if (isExpanded(k, false)) { toggleExpanded(k); } } } }, [expandedMap, toggleExpanded, isExpanded]); useImperativeHandle2(ref, () => ({ toggle, expand, collapse }), [toggle, collapse, expand]); const context2 = useMemo2(() => ({ registerSection, toggleExpanded, isExpanded, animate: animate2, prefix }), [registerSection, toggleExpanded, isExpanded, animate2, prefix]); return createElement2(Comp, _extends({ id }, rest), createElement2(AccordionContext.Provider, { value: context2 }, children)); } var BentoAccordion = forwardRef(BentoAccordionWithRef); BentoAccordion.displayName = "Accordion"; function setExpanded(id, value, expandedMap, expandSingleSection) { let newExpandedMap; if (expandSingleSection && value) { newExpandedMap = { [id]: value }; for (const k in expandedMap) { if (k != id) { newExpandedMap[k] = false; } } } else { newExpandedMap = _extends({}, expandedMap, { [id]: value }); } return newExpandedMap; } function BentoAccordionSection(_ref2) { let { animate: defaultAnimate = false, as: Comp = "section", children, expanded: defaultExpanded = false, id: propId, onExpandStateChange } = _ref2, rest = _objectWithoutPropertiesLoose2(_ref2, _excluded22); const [genId] = useState2(generateSectionId); const id = propId || genId; const [suffix] = useState2(generateRandomId); const [expandedState, setExpandedState] = useState2(defaultExpanded); const [contentIdState, setContentIdState] = useState2(null); const [headerIdState, setHeaderIdState] = useState2(null); const { animate: contextAnimate, isExpanded, prefix, registerSection, toggleExpanded } = useContext2(AccordionContext); const expanded = isExpanded ? isExpanded(id, defaultExpanded) : expandedState; const animate2 = contextAnimate != null ? contextAnimate : defaultAnimate; const contentId = contentIdState || `${prefix || "a"}-content-${id}-${suffix}`; const headerId = headerIdState || `${prefix || "a"}-header-${id}-${suffix}`; const onExpandStateChangeRef = useRef2(null); onExpandStateChangeRef.current = onExpandStateChange; useLayoutEffect2(() => { if (registerSection) { return registerSection(id, defaultExpanded, onExpandStateChangeRef); } }, [registerSection, id, defaultExpanded]); const toggleHandler = useCallback2((opt_expand) => { if (toggleExpanded) { toggleExpanded(id, opt_expand); } else { setExpandedState((prev) => { const newValue = opt_expand != null ? opt_expand : !prev; resolvedPromise().then(() => { const onExpandStateChange2 = onExpandStateChangeRef.current; if (onExpandStateChange2) { onExpandStateChange2(newValue); } }); return newValue; }); } }, [id, toggleExpanded]); const context2 = useMemo2(() => ({ animate: animate2, contentId, headerId, expanded, toggleHandler, setContentId: setContentIdState, setHeaderId: setHeaderIdState }), [animate2, contentId, headerId, expanded, toggleHandler]); return createElement2(Comp, _extends({}, rest), createElement2(SectionContext.Provider, { value: context2 }, children)); } function BentoAccordionHeader(_ref3) { let _propName = propName("class"), _propName2 = propName("tabIndex"), { as: Comp = "div", children, id, role = "button", [_propName]: className = "", [_propName2]: tabIndex = 0 } = _ref3, rest = _objectWithoutPropertiesLoose2(_ref3, ["as", "children", "id", "role", _propName, _propName2].map(_toPropertyKey)); const { contentId, expanded, headerId, setHeaderId, toggleHandler } = useContext2(SectionContext); useLayoutEffect2(() => { if (setHeaderId) { setHeaderId(id); } }, [setHeaderId, id]); return createElement2(Comp, _extends({}, rest, { id: headerId, role, class: `${className} ${$sectionChild} ${$header}`, tabIndex, "aria-controls": contentId, onClick: () => toggleHandler(), "aria-expanded": String(expanded) }), children); } function BentoAccordionContent(_ref4) { let _propName3 = propName("class"), { as: Comp = "div", children, id, role = "region", [_propName3]: className = "" } = _ref4, rest = _objectWithoutPropertiesLoose2(_ref4, ["as", "children", "id", "role", _propName3].map(_toPropertyKey)); const ref = useRef2(null); const hasMountedRef = useRef2(false); const { animate: animate2, contentId, expanded, headerId, setContentId } = useContext2(SectionContext); useEffect2(() => { hasMountedRef.current = true; return () => hasMountedRef.current = false; }, []); useLayoutEffect2(() => { if (setContentId) { setContentId(id); } }, [setContentId, id]); useLayoutEffect2(() => { const hasMounted = hasMountedRef.current; const content = ref.current; if (!animate2 || !hasMounted || !content || !content.animate) { return; } return expanded ? animateExpand(content) : animateCollapse(content); }, [expanded, animate2]); return createElement2(WithAmpContext, { renderable: expanded }, createElement2(Comp, _extends({}, rest, { ref, class: obj_str_default({ [className]: true, [$sectionChild]: true, [$contentHidden]: !expanded }), id: contentId, "aria-labelledby": headerId, role }), children)); } export { BentoAccordion, BentoAccordionContent, BentoAccordionHeader, BentoAccordionSection }; //# sourceMappingURL=component-preact.max.module.js.map