@bentoproject/accordion
Version:
Displays content sections that can be collapsed and expanded.
709 lines (694 loc) • 20 kB
JavaScript
// 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