UNPKG

framer-controller

Version:

Control components and state in Framer X with reusable controllers.

178 lines (175 loc) 6.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const React = require("react"); /** * # createPageControls * Create a unique `usePageControls` hook. * ``` // Without options const usePageControls = createPageControls() // With options const usePageControls = createPageControls({ loop: true, currentPage: 3, history: [1, 0, 2, 0, 1], }) ``` */ function createPageControls(options) { const { currentPage = 0 } = options; const { loop = false, history = [currentPage] } = options; // Initial Store let store = { connected: null, progress: 0, currentPage, pages: [], loop, history, }; // Store const storeSetters = new Set(); const setStoreState = (changes) => { store = Object.assign({}, store, changes); store.progress = store.currentPage / (store.pages.length - 1) || 0; storeSetters.forEach((setter) => setter(store)); }; const set = (values) => setStoreState(values); // Hook const usePageControls = (props) => { // Connect to store const [state, setState] = React.useState(store); React.useEffect(() => () => storeSetters.delete(setState), []); storeSetters.add(setState); // If this hook was called with the props of a scroll component if (props) { const { componentIdentifier } = props; if (!componentIdentifier || componentIdentifier !== 'framer/Page') { console.error("⚠️ You've passed the props of some non-Page component to usePageControls. You should only pass props from the Page component that you're trying to control."); return; } React.useEffect(() => { const componentProps = props.children[0].props; set({ currentPage: componentProps.currentPage, pages: componentProps.children, }); }, [props.currentPage, props.pages]); } /** * Ensure that the next page is an actual page. */ const validatePageIndex = (index) => { const { pages } = state; let nextPage; if (state.loop) { nextPage = (pages.length + index) % pages.length; } else { nextPage = index; } return Math.max(Math.min(nextPage, pages.length - 1), 0); }; /** * Updates the hook when the user changes the Page component's current page. */ const onChangePage = (currentPage) => { if (currentPage !== state.currentPage) { snapToPage(currentPage); } }; /** * Snaps the page component to the page at the provided index. Defaults to * `0`. */ const snapToPage = (index = 0) => { const { pages, history, currentPage } = state; const nextPage = validatePageIndex(index); if (nextPage !== currentPage) { set({ currentPage: nextPage, history: [...history, currentPage], }); } }; /** * Snaps the page component to the next page in a given direction, * either `"right"` or `"left"`. Defaults to `"right"`. */ const snapToNextPage = (direction = 'right') => { const next = nextPage(direction); if (next === null) return; snapToPage(next); }; /** * Snaps the page component to the previous page in the hook's "history" * of visited pages. */ const snapToPreviousPage = () => { const { history, currentPage } = state; if (history.length <= 1) return; const h = [...history]; const nextPage = validatePageIndex(h.pop()); if (nextPage !== currentPage) { set({ currentPage: nextPage, history: h, }); } }; /** * Snaps the page component to the nearest page to a given "progress" * value, where `0` is the Page component's first page and `1` is the last. */ const snapToProgress = (progress) => { const { pages } = state; const index = Math.round((pages.length - 1) * progress); snapToPage(index); }; /** * Returns the index of the next page in the given direction, or else * `null` if no page exists in that direction. */ const nextPage = (direction = 'right') => { const { pages, currentPage } = state; const offset = direction === 'right' ? 1 : -1; if (state.loop) { return (pages.length + (currentPage + offset)) % pages.length; } else { const next = currentPage + offset; return next < 0 || next > pages.length - 1 ? null : next; } }; /** * Returns the index of the previous page in the hook's "history" of * visited pages, or else `null` if no page exists. */ const previousPage = () => { const { history } = state; const h = [...history]; if (h.length <= 1) { return null; } return validatePageIndex(h.pop()); }; return { pages: store.pages, currentPage: store.currentPage, totalPages: store.pages.length, progress: store.progress, history: store.history, nextPage, previousPage, snapToNextPage, snapToPage, snapToPreviousPage, snapToProgress, onChangePage, }; }; return usePageControls; } exports.createPageControls = createPageControls;