| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550 |
- import * as React from 'react';
- import { createContext, useLayoutEffect, useRef, forwardRef, createElement, useContext, useImperativeHandle, useState, useCallback, useEffect, useMemo } from 'react';
- // The "contextmenu" event is not supported as a PointerEvent in all browsers yet, so MouseEvent still need to be handled
- const PanelGroupContext = createContext(null);
- PanelGroupContext.displayName = "PanelGroupContext";
- const DATA_ATTRIBUTES = {
- group: "data-panel-group",
- groupDirection: "data-panel-group-direction",
- groupId: "data-panel-group-id",
- panel: "data-panel",
- panelCollapsible: "data-panel-collapsible",
- panelId: "data-panel-id",
- panelSize: "data-panel-size",
- resizeHandle: "data-resize-handle",
- resizeHandleActive: "data-resize-handle-active",
- resizeHandleEnabled: "data-panel-resize-handle-enabled",
- resizeHandleId: "data-panel-resize-handle-id",
- resizeHandleState: "data-resize-handle-state"
- };
- const PRECISION = 10;
- const useIsomorphicLayoutEffect = useLayoutEffect ;
- const useId = React["useId".toString()];
- const wrappedUseId = typeof useId === "function" ? useId : () => null;
- let counter = 0;
- function useUniqueId(idFromParams = null) {
- const idFromUseId = wrappedUseId();
- const idRef = useRef(idFromParams || idFromUseId || null);
- if (idRef.current === null) {
- idRef.current = "" + counter++;
- }
- return idFromParams !== null && idFromParams !== void 0 ? idFromParams : idRef.current;
- }
- function PanelWithForwardedRef({
- children,
- className: classNameFromProps = "",
- collapsedSize,
- collapsible,
- defaultSize,
- forwardedRef,
- id: idFromProps,
- maxSize,
- minSize,
- onCollapse,
- onExpand,
- onResize,
- order,
- style: styleFromProps,
- tagName: Type = "div",
- ...rest
- }) {
- const context = useContext(PanelGroupContext);
- if (context === null) {
- throw Error(`Panel components must be rendered within a PanelGroup container`);
- }
- const {
- collapsePanel,
- expandPanel,
- getPanelSize,
- getPanelStyle,
- groupId,
- isPanelCollapsed,
- reevaluatePanelConstraints,
- registerPanel,
- resizePanel,
- unregisterPanel
- } = context;
- const panelId = useUniqueId(idFromProps);
- const panelDataRef = useRef({
- callbacks: {
- onCollapse,
- onExpand,
- onResize
- },
- constraints: {
- collapsedSize,
- collapsible,
- defaultSize,
- maxSize,
- minSize
- },
- id: panelId,
- idIsFromProps: idFromProps !== undefined,
- order
- });
- const devWarningsRef = useRef({
- didLogMissingDefaultSizeWarning: false
- });
- // Normally we wouldn't log a warning during render,
- // but effects don't run on the server, so we can't do it there
- {
- if (!devWarningsRef.current.didLogMissingDefaultSizeWarning) ;
- }
- useIsomorphicLayoutEffect(() => {
- const {
- callbacks,
- constraints
- } = panelDataRef.current;
- const prevConstraints = {
- ...constraints
- };
- panelDataRef.current.id = panelId;
- panelDataRef.current.idIsFromProps = idFromProps !== undefined;
- panelDataRef.current.order = order;
- callbacks.onCollapse = onCollapse;
- callbacks.onExpand = onExpand;
- callbacks.onResize = onResize;
- constraints.collapsedSize = collapsedSize;
- constraints.collapsible = collapsible;
- constraints.defaultSize = defaultSize;
- constraints.maxSize = maxSize;
- constraints.minSize = minSize;
- // If constraints have changed, we should revisit panel sizes.
- // This is uncommon but may happen if people are trying to implement pixel based constraints.
- if (prevConstraints.collapsedSize !== constraints.collapsedSize || prevConstraints.collapsible !== constraints.collapsible || prevConstraints.maxSize !== constraints.maxSize || prevConstraints.minSize !== constraints.minSize) {
- reevaluatePanelConstraints(panelDataRef.current, prevConstraints);
- }
- });
- useIsomorphicLayoutEffect(() => {
- const panelData = panelDataRef.current;
- registerPanel(panelData);
- return () => {
- unregisterPanel(panelData);
- };
- }, [order, panelId, registerPanel, unregisterPanel]);
- useImperativeHandle(forwardedRef, () => ({
- collapse: () => {
- collapsePanel(panelDataRef.current);
- },
- expand: minSize => {
- expandPanel(panelDataRef.current, minSize);
- },
- getId() {
- return panelId;
- },
- getSize() {
- return getPanelSize(panelDataRef.current);
- },
- isCollapsed() {
- return isPanelCollapsed(panelDataRef.current);
- },
- isExpanded() {
- return !isPanelCollapsed(panelDataRef.current);
- },
- resize: size => {
- resizePanel(panelDataRef.current, size);
- }
- }), [collapsePanel, expandPanel, getPanelSize, isPanelCollapsed, panelId, resizePanel]);
- const style = getPanelStyle(panelDataRef.current, defaultSize);
- return createElement(Type, {
- ...rest,
- children,
- className: classNameFromProps,
- id: panelId,
- style: {
- ...style,
- ...styleFromProps
- },
- // CSS selectors
- [DATA_ATTRIBUTES.groupId]: groupId,
- [DATA_ATTRIBUTES.panel]: "",
- [DATA_ATTRIBUTES.panelCollapsible]: collapsible || undefined,
- [DATA_ATTRIBUTES.panelId]: panelId,
- [DATA_ATTRIBUTES.panelSize]: parseFloat("" + style.flexGrow).toFixed(1)
- });
- }
- const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
- ...props,
- forwardedRef: ref
- }));
- PanelWithForwardedRef.displayName = "Panel";
- Panel.displayName = "forwardRef(Panel)";
- let nonce;
- function getNonce() {
- return nonce;
- }
- function setNonce(value) {
- nonce = value;
- }
- let currentCursorStyle = null;
- let enabled = true;
- let prevRuleIndex = -1;
- let styleElement = null;
- function disableGlobalCursorStyles() {
- enabled = false;
- }
- function enableGlobalCursorStyles() {
- enabled = true;
- }
- function getCursorStyle(state, constraintFlags) {
- if (constraintFlags) {
- const horizontalMin = (constraintFlags & EXCEEDED_HORIZONTAL_MIN) !== 0;
- const horizontalMax = (constraintFlags & EXCEEDED_HORIZONTAL_MAX) !== 0;
- const verticalMin = (constraintFlags & EXCEEDED_VERTICAL_MIN) !== 0;
- const verticalMax = (constraintFlags & EXCEEDED_VERTICAL_MAX) !== 0;
- if (horizontalMin) {
- if (verticalMin) {
- return "se-resize";
- } else if (verticalMax) {
- return "ne-resize";
- } else {
- return "e-resize";
- }
- } else if (horizontalMax) {
- if (verticalMin) {
- return "sw-resize";
- } else if (verticalMax) {
- return "nw-resize";
- } else {
- return "w-resize";
- }
- } else if (verticalMin) {
- return "s-resize";
- } else if (verticalMax) {
- return "n-resize";
- }
- }
- switch (state) {
- case "horizontal":
- return "ew-resize";
- case "intersection":
- return "move";
- case "vertical":
- return "ns-resize";
- }
- }
- function resetGlobalCursorStyle() {
- if (styleElement !== null) {
- document.head.removeChild(styleElement);
- currentCursorStyle = null;
- styleElement = null;
- prevRuleIndex = -1;
- }
- }
- function setGlobalCursorStyle(state, constraintFlags) {
- var _styleElement$sheet$i, _styleElement$sheet2;
- if (!enabled) {
- return;
- }
- const style = getCursorStyle(state, constraintFlags);
- if (currentCursorStyle === style) {
- return;
- }
- currentCursorStyle = style;
- if (styleElement === null) {
- styleElement = document.createElement("style");
- const nonce = getNonce();
- if (nonce) {
- styleElement.setAttribute("nonce", nonce);
- }
- document.head.appendChild(styleElement);
- }
- if (prevRuleIndex >= 0) {
- var _styleElement$sheet;
- (_styleElement$sheet = styleElement.sheet) === null || _styleElement$sheet === void 0 ? void 0 : _styleElement$sheet.removeRule(prevRuleIndex);
- }
- prevRuleIndex = (_styleElement$sheet$i = (_styleElement$sheet2 = styleElement.sheet) === null || _styleElement$sheet2 === void 0 ? void 0 : _styleElement$sheet2.insertRule(`*{cursor: ${style} !important;}`)) !== null && _styleElement$sheet$i !== void 0 ? _styleElement$sheet$i : -1;
- }
- function isKeyDown(event) {
- return event.type === "keydown";
- }
- function isPointerEvent(event) {
- return event.type.startsWith("pointer");
- }
- function isMouseEvent(event) {
- return event.type.startsWith("mouse");
- }
- function getResizeEventCoordinates(event) {
- if (isPointerEvent(event)) {
- if (event.isPrimary) {
- return {
- x: event.clientX,
- y: event.clientY
- };
- }
- } else if (isMouseEvent(event)) {
- return {
- x: event.clientX,
- y: event.clientY
- };
- }
- return {
- x: Infinity,
- y: Infinity
- };
- }
- function getInputType() {
- if (typeof matchMedia === "function") {
- return matchMedia("(pointer:coarse)").matches ? "coarse" : "fine";
- }
- }
- function intersects(rectOne, rectTwo, strict) {
- if (strict) {
- return rectOne.x < rectTwo.x + rectTwo.width && rectOne.x + rectOne.width > rectTwo.x && rectOne.y < rectTwo.y + rectTwo.height && rectOne.y + rectOne.height > rectTwo.y;
- } else {
- return rectOne.x <= rectTwo.x + rectTwo.width && rectOne.x + rectOne.width >= rectTwo.x && rectOne.y <= rectTwo.y + rectTwo.height && rectOne.y + rectOne.height >= rectTwo.y;
- }
- }
- // Forked from NPM stacking-order@2.0.0
- /**
- * Determine which of two nodes appears in front of the other —
- * if `a` is in front, returns 1, otherwise returns -1
- * @param {HTMLElement | SVGElement} a
- * @param {HTMLElement | SVGElement} b
- */
- function compare(a, b) {
- if (a === b) throw new Error("Cannot compare node with itself");
- const ancestors = {
- a: get_ancestors(a),
- b: get_ancestors(b)
- };
- let common_ancestor;
- // remove shared ancestors
- while (ancestors.a.at(-1) === ancestors.b.at(-1)) {
- a = ancestors.a.pop();
- b = ancestors.b.pop();
- common_ancestor = a;
- }
- assert(common_ancestor, "Stacking order can only be calculated for elements with a common ancestor");
- const z_indexes = {
- a: get_z_index(find_stacking_context(ancestors.a)),
- b: get_z_index(find_stacking_context(ancestors.b))
- };
- if (z_indexes.a === z_indexes.b) {
- const children = common_ancestor.childNodes;
- const furthest_ancestors = {
- a: ancestors.a.at(-1),
- b: ancestors.b.at(-1)
- };
- let i = children.length;
- while (i--) {
- const child = children[i];
- if (child === furthest_ancestors.a) return 1;
- if (child === furthest_ancestors.b) return -1;
- }
- }
- return Math.sign(z_indexes.a - z_indexes.b);
- }
- const props = /\b(?:position|zIndex|opacity|transform|webkitTransform|mixBlendMode|filter|webkitFilter|isolation)\b/;
- /** @param {HTMLElement | SVGElement} node */
- function is_flex_item(node) {
- var _get_parent;
- // @ts-ignore
- const display = getComputedStyle((_get_parent = get_parent(node)) !== null && _get_parent !== void 0 ? _get_parent : node).display;
- return display === "flex" || display === "inline-flex";
- }
- /** @param {HTMLElement | SVGElement} node */
- function creates_stacking_context(node) {
- const style = getComputedStyle(node);
- // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
- if (style.position === "fixed") return true;
- // Forked to fix upstream bug https://github.com/Rich-Harris/stacking-order/issues/3
- // if (
- // (style.zIndex !== "auto" && style.position !== "static") ||
- // is_flex_item(node)
- // )
- if (style.zIndex !== "auto" && (style.position !== "static" || is_flex_item(node))) return true;
- if (+style.opacity < 1) return true;
- if ("transform" in style && style.transform !== "none") return true;
- if ("webkitTransform" in style && style.webkitTransform !== "none") return true;
- if ("mixBlendMode" in style && style.mixBlendMode !== "normal") return true;
- if ("filter" in style && style.filter !== "none") return true;
- if ("webkitFilter" in style && style.webkitFilter !== "none") return true;
- if ("isolation" in style && style.isolation === "isolate") return true;
- if (props.test(style.willChange)) return true;
- // @ts-expect-error
- if (style.webkitOverflowScrolling === "touch") return true;
- return false;
- }
- /** @param {(HTMLElement| SVGElement)[]} nodes */
- function find_stacking_context(nodes) {
- let i = nodes.length;
- while (i--) {
- const node = nodes[i];
- assert(node, "Missing node");
- if (creates_stacking_context(node)) return node;
- }
- return null;
- }
- /** @param {HTMLElement | SVGElement} node */
- function get_z_index(node) {
- return node && Number(getComputedStyle(node).zIndex) || 0;
- }
- /** @param {HTMLElement} node */
- function get_ancestors(node) {
- const ancestors = [];
- while (node) {
- ancestors.push(node);
- // @ts-ignore
- node = get_parent(node);
- }
- return ancestors; // [ node, ... <body>, <html>, document ]
- }
- /** @param {HTMLElement} node */
- function get_parent(node) {
- const {
- parentNode
- } = node;
- if (parentNode && parentNode instanceof ShadowRoot) {
- return parentNode.host;
- }
- return parentNode;
- }
- const EXCEEDED_HORIZONTAL_MIN = 0b0001;
- const EXCEEDED_HORIZONTAL_MAX = 0b0010;
- const EXCEEDED_VERTICAL_MIN = 0b0100;
- const EXCEEDED_VERTICAL_MAX = 0b1000;
- const isCoarsePointer = getInputType() === "coarse";
- let intersectingHandles = [];
- let isPointerDown = false;
- let ownerDocumentCounts = new Map();
- let panelConstraintFlags = new Map();
- const registeredResizeHandlers = new Set();
- function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins, setResizeHandlerState) {
- var _ownerDocumentCounts$;
- const {
- ownerDocument
- } = element;
- const data = {
- direction,
- element,
- hitAreaMargins,
- setResizeHandlerState
- };
- const count = (_ownerDocumentCounts$ = ownerDocumentCounts.get(ownerDocument)) !== null && _ownerDocumentCounts$ !== void 0 ? _ownerDocumentCounts$ : 0;
- ownerDocumentCounts.set(ownerDocument, count + 1);
- registeredResizeHandlers.add(data);
- updateListeners();
- return function unregisterResizeHandle() {
- var _ownerDocumentCounts$2;
- panelConstraintFlags.delete(resizeHandleId);
- registeredResizeHandlers.delete(data);
- const count = (_ownerDocumentCounts$2 = ownerDocumentCounts.get(ownerDocument)) !== null && _ownerDocumentCounts$2 !== void 0 ? _ownerDocumentCounts$2 : 1;
- ownerDocumentCounts.set(ownerDocument, count - 1);
- updateListeners();
- if (count === 1) {
- ownerDocumentCounts.delete(ownerDocument);
- }
- // If the resize handle that is currently unmounting is intersecting with the pointer,
- // update the global pointer to account for the change
- if (intersectingHandles.includes(data)) {
- const index = intersectingHandles.indexOf(data);
- if (index >= 0) {
- intersectingHandles.splice(index, 1);
- }
- updateCursor();
- // Also instruct the handle to stop dragging; this prevents the parent group from being left in an inconsistent state
- // See github.com/bvaughn/react-resizable-panels/issues/402
- setResizeHandlerState("up", true, null);
- }
- };
- }
- function handlePointerDown(event) {
- const {
- target
- } = event;
- const {
- x,
- y
- } = getResizeEventCoordinates(event);
- isPointerDown = true;
- recalculateIntersectingHandles({
- target,
- x,
- y
- });
- updateListeners();
- if (intersectingHandles.length > 0) {
- updateResizeHandlerStates("down", event);
- event.preventDefault();
- if (!isWithinResizeHandle(target)) {
- event.stopImmediatePropagation();
- }
- }
- }
- function handlePointerMove(event) {
- const {
- x,
- y
- } = getResizeEventCoordinates(event);
- // Edge case (see #340)
- // Detect when the pointer has been released outside an iframe on a different domain
- if (isPointerDown && event.buttons === 0) {
- isPointerDown = false;
- updateResizeHandlerStates("up", event);
- }
- if (!isPointerDown) {
- const {
- target
- } = event;
- // Recalculate intersecting handles whenever the pointer moves, except if it has already been pressed
- // at that point, the handles may not move with the pointer (depending on constraints)
- // but the same set of active handles should be locked until the pointer is released
- recalculateIntersectingHandles({
- target,
- x,
- y
- });
- }
- updateResizeHandlerStates("move", event);
- // Update cursor based on return value(s) from active handles
- updateCursor();
- if (intersectingHandles.length > 0) {
- event.preventDefault();
- }
- }
- function handlePointerUp(event) {
- const {
- target
- } = event;
- const {
- x,
- y
- } = getResizeEventCoordinates(event);
- panelConstraintFlags.clear();
- isPointerDown = false;
- if (intersectingHandles.length > 0) {
- event.preventDefault();
- if (!isWithinResizeHandle(target)) {
- event.stopImmediatePropagation();
- }
- }
- updateResizeHandlerStates("up", event);
- recalculateIntersectingHandles({
- target,
- x,
- y
- });
- updateCursor();
- updateListeners();
- }
- function isWithinResizeHandle(element) {
- let currentElement = element;
- while (currentElement) {
- if (currentElement.hasAttribute(DATA_ATTRIBUTES.resizeHandle)) {
- return true;
- }
- currentElement = currentElement.parentElement;
- }
- return false;
- }
- function recalculateIntersectingHandles({
- target,
- x,
- y
- }) {
- intersectingHandles.splice(0);
- let targetElement = null;
- if (target instanceof HTMLElement || target instanceof SVGElement) {
- targetElement = target;
- }
- registeredResizeHandlers.forEach(data => {
- const {
- element: dragHandleElement,
- hitAreaMargins
- } = data;
- const dragHandleRect = dragHandleElement.getBoundingClientRect();
- const {
- bottom,
- left,
- right,
- top
- } = dragHandleRect;
- const margin = isCoarsePointer ? hitAreaMargins.coarse : hitAreaMargins.fine;
- const eventIntersects = x >= left - margin && x <= right + margin && y >= top - margin && y <= bottom + margin;
- if (eventIntersects) {
- // TRICKY
- // We listen for pointers events at the root in order to support hit area margins
- // (determining when the pointer is close enough to an element to be considered a "hit")
- // Clicking on an element "above" a handle (e.g. a modal) should prevent a hit though
- // so at this point we need to compare stacking order of a potentially intersecting drag handle,
- // and the element that was actually clicked/touched
- if (targetElement !== null && document.contains(targetElement) && dragHandleElement !== targetElement && !dragHandleElement.contains(targetElement) && !targetElement.contains(dragHandleElement) &&
- // Calculating stacking order has a cost, so we should avoid it if possible
- // That is why we only check potentially intersecting handles,
- // and why we skip if the event target is within the handle's DOM
- compare(targetElement, dragHandleElement) > 0) {
- // If the target is above the drag handle, then we also need to confirm they overlap
- // If they are beside each other (e.g. a panel and its drag handle) then the handle is still interactive
- //
- // It's not enough to compare only the target
- // The target might be a small element inside of a larger container
- // (For example, a SPAN or a DIV inside of a larger modal dialog)
- let currentElement = targetElement;
- let didIntersect = false;
- while (currentElement) {
- if (currentElement.contains(dragHandleElement)) {
- break;
- } else if (intersects(currentElement.getBoundingClientRect(), dragHandleRect, true)) {
- didIntersect = true;
- break;
- }
- currentElement = currentElement.parentElement;
- }
- if (didIntersect) {
- return;
- }
- }
- intersectingHandles.push(data);
- }
- });
- }
- function reportConstraintsViolation(resizeHandleId, flag) {
- panelConstraintFlags.set(resizeHandleId, flag);
- }
- function updateCursor() {
- let intersectsHorizontal = false;
- let intersectsVertical = false;
- intersectingHandles.forEach(data => {
- const {
- direction
- } = data;
- if (direction === "horizontal") {
- intersectsHorizontal = true;
- } else {
- intersectsVertical = true;
- }
- });
- let constraintFlags = 0;
- panelConstraintFlags.forEach(flag => {
- constraintFlags |= flag;
- });
- if (intersectsHorizontal && intersectsVertical) {
- setGlobalCursorStyle("intersection", constraintFlags);
- } else if (intersectsHorizontal) {
- setGlobalCursorStyle("horizontal", constraintFlags);
- } else if (intersectsVertical) {
- setGlobalCursorStyle("vertical", constraintFlags);
- } else {
- resetGlobalCursorStyle();
- }
- }
- let listenersAbortController = new AbortController();
- function updateListeners() {
- listenersAbortController.abort();
- listenersAbortController = new AbortController();
- const options = {
- capture: true,
- signal: listenersAbortController.signal
- };
- if (!registeredResizeHandlers.size) {
- return;
- }
- if (isPointerDown) {
- if (intersectingHandles.length > 0) {
- ownerDocumentCounts.forEach((count, ownerDocument) => {
- const {
- body
- } = ownerDocument;
- if (count > 0) {
- body.addEventListener("contextmenu", handlePointerUp, options);
- body.addEventListener("pointerleave", handlePointerMove, options);
- body.addEventListener("pointermove", handlePointerMove, options);
- }
- });
- }
- window.addEventListener("pointerup", handlePointerUp, options);
- window.addEventListener("pointercancel", handlePointerUp, options);
- } else {
- ownerDocumentCounts.forEach((count, ownerDocument) => {
- const {
- body
- } = ownerDocument;
- if (count > 0) {
- body.addEventListener("pointerdown", handlePointerDown, options);
- body.addEventListener("pointermove", handlePointerMove, options);
- }
- });
- }
- }
- function updateResizeHandlerStates(action, event) {
- registeredResizeHandlers.forEach(data => {
- const {
- setResizeHandlerState
- } = data;
- const isActive = intersectingHandles.includes(data);
- setResizeHandlerState(action, isActive, event);
- });
- }
- function useForceUpdate() {
- const [_, setCount] = useState(0);
- return useCallback(() => setCount(prevCount => prevCount + 1), []);
- }
- function assert(expectedCondition, message) {
- if (!expectedCondition) {
- console.error(message);
- throw Error(message);
- }
- }
- function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
- if (actual.toFixed(fractionDigits) === expected.toFixed(fractionDigits)) {
- return 0;
- } else {
- return actual > expected ? 1 : -1;
- }
- }
- function fuzzyNumbersEqual$1(actual, expected, fractionDigits = PRECISION) {
- return fuzzyCompareNumbers(actual, expected, fractionDigits) === 0;
- }
- function fuzzyNumbersEqual(actual, expected, fractionDigits) {
- return fuzzyCompareNumbers(actual, expected, fractionDigits) === 0;
- }
- function fuzzyLayoutsEqual(actual, expected, fractionDigits) {
- if (actual.length !== expected.length) {
- return false;
- }
- for (let index = 0; index < actual.length; index++) {
- const actualSize = actual[index];
- const expectedSize = expected[index];
- if (!fuzzyNumbersEqual(actualSize, expectedSize, fractionDigits)) {
- return false;
- }
- }
- return true;
- }
- // Panel size must be in percentages; pixel values should be pre-converted
- function resizePanel({
- panelConstraints: panelConstraintsArray,
- panelIndex,
- size
- }) {
- const panelConstraints = panelConstraintsArray[panelIndex];
- assert(panelConstraints != null, `Panel constraints not found for index ${panelIndex}`);
- let {
- collapsedSize = 0,
- collapsible,
- maxSize = 100,
- minSize = 0
- } = panelConstraints;
- if (fuzzyCompareNumbers(size, minSize) < 0) {
- if (collapsible) {
- // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
- const halfwayPoint = (collapsedSize + minSize) / 2;
- if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
- size = collapsedSize;
- } else {
- size = minSize;
- }
- } else {
- size = minSize;
- }
- }
- size = Math.min(maxSize, size);
- size = parseFloat(size.toFixed(PRECISION));
- return size;
- }
- // All units must be in percentages; pixel values should be pre-converted
- function adjustLayoutByDelta({
- delta,
- initialLayout,
- panelConstraints: panelConstraintsArray,
- pivotIndices,
- prevLayout,
- trigger
- }) {
- if (fuzzyNumbersEqual(delta, 0)) {
- return initialLayout;
- }
- const nextLayout = [...initialLayout];
- const [firstPivotIndex, secondPivotIndex] = pivotIndices;
- assert(firstPivotIndex != null, "Invalid first pivot index");
- assert(secondPivotIndex != null, "Invalid second pivot index");
- let deltaApplied = 0;
- // const DEBUG = [];
- // DEBUG.push(`adjustLayoutByDelta()`);
- // DEBUG.push(` initialLayout: ${initialLayout.join(", ")}`);
- // DEBUG.push(` prevLayout: ${prevLayout.join(", ")}`);
- // DEBUG.push(` delta: ${delta}`);
- // DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
- // DEBUG.push(` trigger: ${trigger}`);
- // DEBUG.push("");
- // A resizing panel affects the panels before or after it.
- //
- // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
- // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
- //
- // A positive delta means the panel(s) immediately before the resize handle should "expand".
- // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
- {
- // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
- // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
- if (trigger === "keyboard") {
- {
- // Check if we should expand a collapsed panel
- const index = delta < 0 ? secondPivotIndex : firstPivotIndex;
- const panelConstraints = panelConstraintsArray[index];
- assert(panelConstraints, `Panel constraints not found for index ${index}`);
- const {
- collapsedSize = 0,
- collapsible,
- minSize = 0
- } = panelConstraints;
- // DEBUG.push(`edge case check 1: ${index}`);
- // DEBUG.push(` -> collapsible? ${collapsible}`);
- if (collapsible) {
- const prevSize = initialLayout[index];
- assert(prevSize != null, `Previous layout not found for panel index ${index}`);
- if (fuzzyNumbersEqual(prevSize, collapsedSize)) {
- const localDelta = minSize - prevSize;
- // DEBUG.push(` -> expand delta: ${localDelta}`);
- if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
- delta = delta < 0 ? 0 - localDelta : localDelta;
- // DEBUG.push(` -> delta: ${delta}`);
- }
- }
- }
- }
- {
- // Check if we should collapse a panel at its minimum size
- const index = delta < 0 ? firstPivotIndex : secondPivotIndex;
- const panelConstraints = panelConstraintsArray[index];
- assert(panelConstraints, `No panel constraints found for index ${index}`);
- const {
- collapsedSize = 0,
- collapsible,
- minSize = 0
- } = panelConstraints;
- // DEBUG.push(`edge case check 2: ${index}`);
- // DEBUG.push(` -> collapsible? ${collapsible}`);
- if (collapsible) {
- const prevSize = initialLayout[index];
- assert(prevSize != null, `Previous layout not found for panel index ${index}`);
- if (fuzzyNumbersEqual(prevSize, minSize)) {
- const localDelta = prevSize - collapsedSize;
- // DEBUG.push(` -> expand delta: ${localDelta}`);
- if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
- delta = delta < 0 ? 0 - localDelta : localDelta;
- // DEBUG.push(` -> delta: ${delta}`);
- }
- }
- }
- }
- }
- // DEBUG.push("");
- }
- {
- // Pre-calculate max available delta in the opposite direction of our pivot.
- // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
- // If this amount is less than the requested delta, adjust the requested delta.
- // If this amount is greater than the requested delta, that's useful information too–
- // as an expanding panel might change from collapsed to min size.
- const increment = delta < 0 ? 1 : -1;
- let index = delta < 0 ? secondPivotIndex : firstPivotIndex;
- let maxAvailableDelta = 0;
- // DEBUG.push("pre calc...");
- while (true) {
- const prevSize = initialLayout[index];
- assert(prevSize != null, `Previous layout not found for panel index ${index}`);
- const maxSafeSize = resizePanel({
- panelConstraints: panelConstraintsArray,
- panelIndex: index,
- size: 100
- });
- const delta = maxSafeSize - prevSize;
- // DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
- maxAvailableDelta += delta;
- index += increment;
- if (index < 0 || index >= panelConstraintsArray.length) {
- break;
- }
- }
- // DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
- const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
- delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
- // DEBUG.push(` -> adjusted delta: ${delta}`);
- // DEBUG.push("");
- }
- {
- // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
- const pivotIndex = delta < 0 ? firstPivotIndex : secondPivotIndex;
- let index = pivotIndex;
- while (index >= 0 && index < panelConstraintsArray.length) {
- const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
- const prevSize = initialLayout[index];
- assert(prevSize != null, `Previous layout not found for panel index ${index}`);
- const unsafeSize = prevSize - deltaRemaining;
- const safeSize = resizePanel({
- panelConstraints: panelConstraintsArray,
- panelIndex: index,
- size: unsafeSize
- });
- if (!fuzzyNumbersEqual(prevSize, safeSize)) {
- deltaApplied += prevSize - safeSize;
- nextLayout[index] = safeSize;
- if (deltaApplied.toPrecision(3).localeCompare(Math.abs(delta).toPrecision(3), undefined, {
- numeric: true
- }) >= 0) {
- break;
- }
- }
- if (delta < 0) {
- index--;
- } else {
- index++;
- }
- }
- }
- // DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
- // DEBUG.push(` deltaApplied: ${deltaApplied}`);
- // DEBUG.push("");
- // If we were unable to resize any of the panels panels, return the previous state.
- // This will essentially bailout and ignore e.g. drags past a panel's boundaries
- if (fuzzyLayoutsEqual(prevLayout, nextLayout)) {
- // DEBUG.push(`bailout to previous layout: ${prevLayout.join(", ")}`);
- // console.log(DEBUG.join("\n"));
- return prevLayout;
- }
- {
- // Now distribute the applied delta to the panels in the other direction
- const pivotIndex = delta < 0 ? secondPivotIndex : firstPivotIndex;
- const prevSize = initialLayout[pivotIndex];
- assert(prevSize != null, `Previous layout not found for panel index ${pivotIndex}`);
- const unsafeSize = prevSize + deltaApplied;
- const safeSize = resizePanel({
- panelConstraints: panelConstraintsArray,
- panelIndex: pivotIndex,
- size: unsafeSize
- });
- // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
- nextLayout[pivotIndex] = safeSize;
- // Edge case where expanding or contracting one panel caused another one to change collapsed state
- if (!fuzzyNumbersEqual(safeSize, unsafeSize)) {
- let deltaRemaining = unsafeSize - safeSize;
- const pivotIndex = delta < 0 ? secondPivotIndex : firstPivotIndex;
- let index = pivotIndex;
- while (index >= 0 && index < panelConstraintsArray.length) {
- const prevSize = nextLayout[index];
- assert(prevSize != null, `Previous layout not found for panel index ${index}`);
- const unsafeSize = prevSize + deltaRemaining;
- const safeSize = resizePanel({
- panelConstraints: panelConstraintsArray,
- panelIndex: index,
- size: unsafeSize
- });
- if (!fuzzyNumbersEqual(prevSize, safeSize)) {
- deltaRemaining -= safeSize - prevSize;
- nextLayout[index] = safeSize;
- }
- if (fuzzyNumbersEqual(deltaRemaining, 0)) {
- break;
- }
- if (delta > 0) {
- index--;
- } else {
- index++;
- }
- }
- }
- }
- // DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
- // DEBUG.push(` deltaApplied: ${deltaApplied}`);
- // DEBUG.push("");
- const totalSize = nextLayout.reduce((total, size) => size + total, 0);
- // DEBUG.push(`total size: ${totalSize}`);
- // If our new layout doesn't add up to 100%, that means the requested delta can't be applied
- // In that case, fall back to our most recent valid layout
- if (!fuzzyNumbersEqual(totalSize, 100)) {
- // DEBUG.push(`bailout to previous layout: ${prevLayout.join(", ")}`);
- // console.log(DEBUG.join("\n"));
- return prevLayout;
- }
- // console.log(DEBUG.join("\n"));
- return nextLayout;
- }
- function calculateAriaValues({
- layout,
- panelsArray,
- pivotIndices
- }) {
- let currentMinSize = 0;
- let currentMaxSize = 100;
- let totalMinSize = 0;
- let totalMaxSize = 0;
- const firstIndex = pivotIndices[0];
- assert(firstIndex != null, "No pivot index found");
- // A panel's effective min/max sizes also need to account for other panel's sizes.
- panelsArray.forEach((panelData, index) => {
- const {
- constraints
- } = panelData;
- const {
- maxSize = 100,
- minSize = 0
- } = constraints;
- if (index === firstIndex) {
- currentMinSize = minSize;
- currentMaxSize = maxSize;
- } else {
- totalMinSize += minSize;
- totalMaxSize += maxSize;
- }
- });
- const valueMax = Math.min(currentMaxSize, 100 - totalMinSize);
- const valueMin = Math.max(currentMinSize, 100 - totalMaxSize);
- const valueNow = layout[firstIndex];
- return {
- valueMax,
- valueMin,
- valueNow
- };
- }
- function getResizeHandleElementsForGroup(groupId, scope = document) {
- return Array.from(scope.querySelectorAll(`[${DATA_ATTRIBUTES.resizeHandleId}][data-panel-group-id="${groupId}"]`));
- }
- function getResizeHandleElementIndex(groupId, id, scope = document) {
- const handles = getResizeHandleElementsForGroup(groupId, scope);
- const index = handles.findIndex(handle => handle.getAttribute(DATA_ATTRIBUTES.resizeHandleId) === id);
- return index !== null && index !== void 0 ? index : null;
- }
- function determinePivotIndices(groupId, dragHandleId, panelGroupElement) {
- const index = getResizeHandleElementIndex(groupId, dragHandleId, panelGroupElement);
- return index != null ? [index, index + 1] : [-1, -1];
- }
- function getPanelGroupElement(id, rootElement = document) {
- var _dataset;
- //If the root element is the PanelGroup
- if (rootElement instanceof HTMLElement && (rootElement === null || rootElement === void 0 ? void 0 : (_dataset = rootElement.dataset) === null || _dataset === void 0 ? void 0 : _dataset.panelGroupId) == id) {
- return rootElement;
- }
- //Else query children
- const element = rootElement.querySelector(`[data-panel-group][data-panel-group-id="${id}"]`);
- if (element) {
- return element;
- }
- return null;
- }
- function getResizeHandleElement(id, scope = document) {
- const element = scope.querySelector(`[${DATA_ATTRIBUTES.resizeHandleId}="${id}"]`);
- if (element) {
- return element;
- }
- return null;
- }
- function getResizeHandlePanelIds(groupId, handleId, panelsArray, scope = document) {
- var _panelsArray$index$id, _panelsArray$index, _panelsArray$id, _panelsArray;
- const handle = getResizeHandleElement(handleId, scope);
- const handles = getResizeHandleElementsForGroup(groupId, scope);
- const index = handle ? handles.indexOf(handle) : -1;
- const idBefore = (_panelsArray$index$id = (_panelsArray$index = panelsArray[index]) === null || _panelsArray$index === void 0 ? void 0 : _panelsArray$index.id) !== null && _panelsArray$index$id !== void 0 ? _panelsArray$index$id : null;
- const idAfter = (_panelsArray$id = (_panelsArray = panelsArray[index + 1]) === null || _panelsArray === void 0 ? void 0 : _panelsArray.id) !== null && _panelsArray$id !== void 0 ? _panelsArray$id : null;
- return [idBefore, idAfter];
- }
- // https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
- function useWindowSplitterPanelGroupBehavior({
- committedValuesRef,
- eagerValuesRef,
- groupId,
- layout,
- panelDataArray,
- panelGroupElement,
- setLayout
- }) {
- const devWarningsRef = useRef({
- didWarnAboutMissingResizeHandle: false
- });
- useIsomorphicLayoutEffect(() => {
- if (!panelGroupElement) {
- return;
- }
- const resizeHandleElements = getResizeHandleElementsForGroup(groupId, panelGroupElement);
- for (let index = 0; index < panelDataArray.length - 1; index++) {
- const {
- valueMax,
- valueMin,
- valueNow
- } = calculateAriaValues({
- layout,
- panelsArray: panelDataArray,
- pivotIndices: [index, index + 1]
- });
- const resizeHandleElement = resizeHandleElements[index];
- if (resizeHandleElement == null) {
- {
- const {
- didWarnAboutMissingResizeHandle
- } = devWarningsRef.current;
- if (!didWarnAboutMissingResizeHandle) {
- devWarningsRef.current.didWarnAboutMissingResizeHandle = true;
- console.warn(`WARNING: Missing resize handle for PanelGroup "${groupId}"`);
- }
- }
- } else {
- const panelData = panelDataArray[index];
- assert(panelData, `No panel data found for index "${index}"`);
- resizeHandleElement.setAttribute("aria-controls", panelData.id);
- resizeHandleElement.setAttribute("aria-valuemax", "" + Math.round(valueMax));
- resizeHandleElement.setAttribute("aria-valuemin", "" + Math.round(valueMin));
- resizeHandleElement.setAttribute("aria-valuenow", valueNow != null ? "" + Math.round(valueNow) : "");
- }
- }
- return () => {
- resizeHandleElements.forEach((resizeHandleElement, index) => {
- resizeHandleElement.removeAttribute("aria-controls");
- resizeHandleElement.removeAttribute("aria-valuemax");
- resizeHandleElement.removeAttribute("aria-valuemin");
- resizeHandleElement.removeAttribute("aria-valuenow");
- });
- };
- }, [groupId, layout, panelDataArray, panelGroupElement]);
- useEffect(() => {
- if (!panelGroupElement) {
- return;
- }
- const eagerValues = eagerValuesRef.current;
- assert(eagerValues, `Eager values not found`);
- const {
- panelDataArray
- } = eagerValues;
- const groupElement = getPanelGroupElement(groupId, panelGroupElement);
- assert(groupElement != null, `No group found for id "${groupId}"`);
- const handles = getResizeHandleElementsForGroup(groupId, panelGroupElement);
- assert(handles, `No resize handles found for group id "${groupId}"`);
- const cleanupFunctions = handles.map(handle => {
- const handleId = handle.getAttribute(DATA_ATTRIBUTES.resizeHandleId);
- assert(handleId, `Resize handle element has no handle id attribute`);
- const [idBefore, idAfter] = getResizeHandlePanelIds(groupId, handleId, panelDataArray, panelGroupElement);
- if (idBefore == null || idAfter == null) {
- return () => {};
- }
- const onKeyDown = event => {
- if (event.defaultPrevented) {
- return;
- }
- switch (event.key) {
- case "Enter":
- {
- event.preventDefault();
- const index = panelDataArray.findIndex(panelData => panelData.id === idBefore);
- if (index >= 0) {
- const panelData = panelDataArray[index];
- assert(panelData, `No panel data found for index ${index}`);
- const size = layout[index];
- const {
- collapsedSize = 0,
- collapsible,
- minSize = 0
- } = panelData.constraints;
- if (size != null && collapsible) {
- const nextLayout = adjustLayoutByDelta({
- delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
- initialLayout: layout,
- panelConstraints: panelDataArray.map(panelData => panelData.constraints),
- pivotIndices: determinePivotIndices(groupId, handleId, panelGroupElement),
- prevLayout: layout,
- trigger: "keyboard"
- });
- if (layout !== nextLayout) {
- setLayout(nextLayout);
- }
- }
- }
- break;
- }
- }
- };
- handle.addEventListener("keydown", onKeyDown);
- return () => {
- handle.removeEventListener("keydown", onKeyDown);
- };
- });
- return () => {
- cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
- };
- }, [panelGroupElement, committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
- }
- function areEqual(arrayA, arrayB) {
- if (arrayA.length !== arrayB.length) {
- return false;
- }
- for (let index = 0; index < arrayA.length; index++) {
- if (arrayA[index] !== arrayB[index]) {
- return false;
- }
- }
- return true;
- }
- function getResizeEventCursorPosition(direction, event) {
- const isHorizontal = direction === "horizontal";
- const {
- x,
- y
- } = getResizeEventCoordinates(event);
- return isHorizontal ? x : y;
- }
- function calculateDragOffsetPercentage(event, dragHandleId, direction, initialDragState, panelGroupElement) {
- const isHorizontal = direction === "horizontal";
- const handleElement = getResizeHandleElement(dragHandleId, panelGroupElement);
- assert(handleElement, `No resize handle element found for id "${dragHandleId}"`);
- const groupId = handleElement.getAttribute(DATA_ATTRIBUTES.groupId);
- assert(groupId, `Resize handle element has no group id attribute`);
- let {
- initialCursorPosition
- } = initialDragState;
- const cursorPosition = getResizeEventCursorPosition(direction, event);
- const groupElement = getPanelGroupElement(groupId, panelGroupElement);
- assert(groupElement, `No group element found for id "${groupId}"`);
- const groupRect = groupElement.getBoundingClientRect();
- const groupSizeInPixels = isHorizontal ? groupRect.width : groupRect.height;
- const offsetPixels = cursorPosition - initialCursorPosition;
- const offsetPercentage = offsetPixels / groupSizeInPixels * 100;
- return offsetPercentage;
- }
- // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX
- function calculateDeltaPercentage(event, dragHandleId, direction, initialDragState, keyboardResizeBy, panelGroupElement) {
- if (isKeyDown(event)) {
- const isHorizontal = direction === "horizontal";
- let delta = 0;
- if (event.shiftKey) {
- delta = 100;
- } else if (keyboardResizeBy != null) {
- delta = keyboardResizeBy;
- } else {
- delta = 10;
- }
- let movement = 0;
- switch (event.key) {
- case "ArrowDown":
- movement = isHorizontal ? 0 : delta;
- break;
- case "ArrowLeft":
- movement = isHorizontal ? -delta : 0;
- break;
- case "ArrowRight":
- movement = isHorizontal ? delta : 0;
- break;
- case "ArrowUp":
- movement = isHorizontal ? 0 : -delta;
- break;
- case "End":
- movement = 100;
- break;
- case "Home":
- movement = -100;
- break;
- }
- return movement;
- } else {
- if (initialDragState == null) {
- return 0;
- }
- return calculateDragOffsetPercentage(event, dragHandleId, direction, initialDragState, panelGroupElement);
- }
- }
- function calculateUnsafeDefaultLayout({
- panelDataArray
- }) {
- const layout = Array(panelDataArray.length);
- const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
- let numPanelsWithSizes = 0;
- let remainingSize = 100;
- // Distribute default sizes first
- for (let index = 0; index < panelDataArray.length; index++) {
- const panelConstraints = panelConstraintsArray[index];
- assert(panelConstraints, `Panel constraints not found for index ${index}`);
- const {
- defaultSize
- } = panelConstraints;
- if (defaultSize != null) {
- numPanelsWithSizes++;
- layout[index] = defaultSize;
- remainingSize -= defaultSize;
- }
- }
- // Remaining size should be distributed evenly between panels without default sizes
- for (let index = 0; index < panelDataArray.length; index++) {
- const panelConstraints = panelConstraintsArray[index];
- assert(panelConstraints, `Panel constraints not found for index ${index}`);
- const {
- defaultSize
- } = panelConstraints;
- if (defaultSize != null) {
- continue;
- }
- const numRemainingPanels = panelDataArray.length - numPanelsWithSizes;
- const size = remainingSize / numRemainingPanels;
- numPanelsWithSizes++;
- layout[index] = size;
- remainingSize -= size;
- }
- return layout;
- }
- // Layout should be pre-converted into percentages
- function callPanelCallbacks(panelsArray, layout, panelIdToLastNotifiedSizeMap) {
- layout.forEach((size, index) => {
- const panelData = panelsArray[index];
- assert(panelData, `Panel data not found for index ${index}`);
- const {
- callbacks,
- constraints,
- id: panelId
- } = panelData;
- const {
- collapsedSize = 0,
- collapsible
- } = constraints;
- const lastNotifiedSize = panelIdToLastNotifiedSizeMap[panelId];
- if (lastNotifiedSize == null || size !== lastNotifiedSize) {
- panelIdToLastNotifiedSizeMap[panelId] = size;
- const {
- onCollapse,
- onExpand,
- onResize
- } = callbacks;
- if (onResize) {
- onResize(size, lastNotifiedSize);
- }
- if (collapsible && (onCollapse || onExpand)) {
- if (onExpand && (lastNotifiedSize == null || fuzzyNumbersEqual$1(lastNotifiedSize, collapsedSize)) && !fuzzyNumbersEqual$1(size, collapsedSize)) {
- onExpand();
- }
- if (onCollapse && (lastNotifiedSize == null || !fuzzyNumbersEqual$1(lastNotifiedSize, collapsedSize)) && fuzzyNumbersEqual$1(size, collapsedSize)) {
- onCollapse();
- }
- }
- }
- });
- }
- function compareLayouts(a, b) {
- if (a.length !== b.length) {
- return false;
- } else {
- for (let index = 0; index < a.length; index++) {
- if (a[index] != b[index]) {
- return false;
- }
- }
- }
- return true;
- }
- // This method returns a number between 1 and 100 representing
- // the % of the group's overall space this panel should occupy.
- function computePanelFlexBoxStyle({
- defaultSize,
- dragState,
- layout,
- panelData,
- panelIndex,
- precision = 3
- }) {
- const size = layout[panelIndex];
- let flexGrow;
- if (size == null) {
- // Initial render (before panels have registered themselves)
- // In order to support server rendering, fall back to default size if provided
- flexGrow = defaultSize != undefined ? defaultSize.toPrecision(precision) : "1";
- } else if (panelData.length === 1) {
- // Special case: Single panel group should always fill full width/height
- flexGrow = "1";
- } else {
- flexGrow = size.toPrecision(precision);
- }
- return {
- flexBasis: 0,
- flexGrow,
- flexShrink: 1,
- // Without this, Panel sizes may be unintentionally overridden by their content
- overflow: "hidden",
- // Disable pointer events inside of a panel during resize
- // This avoid edge cases like nested iframes
- pointerEvents: dragState !== null ? "none" : undefined
- };
- }
- function debounce(callback, durationMs = 10) {
- let timeoutId = null;
- let callable = (...args) => {
- if (timeoutId !== null) {
- clearTimeout(timeoutId);
- }
- timeoutId = setTimeout(() => {
- callback(...args);
- }, durationMs);
- };
- return callable;
- }
- // PanelGroup might be rendering in a server-side environment where localStorage is not available
- // or on a browser with cookies/storage disabled.
- // In either case, this function avoids accessing localStorage until needed,
- // and avoids throwing user-visible errors.
- function initializeDefaultStorage(storageObject) {
- try {
- if (typeof localStorage !== "undefined") {
- // Bypass this check for future calls
- storageObject.getItem = name => {
- return localStorage.getItem(name);
- };
- storageObject.setItem = (name, value) => {
- localStorage.setItem(name, value);
- };
- } else {
- throw new Error("localStorage not supported in this environment");
- }
- } catch (error) {
- console.error(error);
- storageObject.getItem = () => null;
- storageObject.setItem = () => {};
- }
- }
- function getPanelGroupKey(autoSaveId) {
- return `react-resizable-panels:${autoSaveId}`;
- }
- // Note that Panel ids might be user-provided (stable) or useId generated (non-deterministic)
- // so they should not be used as part of the serialization key.
- // Using the min/max size attributes should work well enough as a backup.
- // Pre-sorting by minSize allows remembering layouts even if panels are re-ordered/dragged.
- function getPanelKey(panels) {
- return panels.map(panel => {
- const {
- constraints,
- id,
- idIsFromProps,
- order
- } = panel;
- if (idIsFromProps) {
- return id;
- } else {
- return order ? `${order}:${JSON.stringify(constraints)}` : JSON.stringify(constraints);
- }
- }).sort((a, b) => a.localeCompare(b)).join(",");
- }
- function loadSerializedPanelGroupState(autoSaveId, storage) {
- try {
- const panelGroupKey = getPanelGroupKey(autoSaveId);
- const serialized = storage.getItem(panelGroupKey);
- if (serialized) {
- const parsed = JSON.parse(serialized);
- if (typeof parsed === "object" && parsed != null) {
- return parsed;
- }
- }
- } catch (error) {}
- return null;
- }
- function loadPanelGroupState(autoSaveId, panels, storage) {
- var _loadSerializedPanelG, _state$panelKey;
- const state = (_loadSerializedPanelG = loadSerializedPanelGroupState(autoSaveId, storage)) !== null && _loadSerializedPanelG !== void 0 ? _loadSerializedPanelG : {};
- const panelKey = getPanelKey(panels);
- return (_state$panelKey = state[panelKey]) !== null && _state$panelKey !== void 0 ? _state$panelKey : null;
- }
- function savePanelGroupState(autoSaveId, panels, panelSizesBeforeCollapse, sizes, storage) {
- var _loadSerializedPanelG2;
- const panelGroupKey = getPanelGroupKey(autoSaveId);
- const panelKey = getPanelKey(panels);
- const state = (_loadSerializedPanelG2 = loadSerializedPanelGroupState(autoSaveId, storage)) !== null && _loadSerializedPanelG2 !== void 0 ? _loadSerializedPanelG2 : {};
- state[panelKey] = {
- expandToSizes: Object.fromEntries(panelSizesBeforeCollapse.entries()),
- layout: sizes
- };
- try {
- storage.setItem(panelGroupKey, JSON.stringify(state));
- } catch (error) {
- console.error(error);
- }
- }
- function validatePanelConstraints({
- panelConstraints: panelConstraintsArray,
- panelId,
- panelIndex
- }) {
- {
- const warnings = [];
- const panelConstraints = panelConstraintsArray[panelIndex];
- assert(panelConstraints, `No panel constraints found for index ${panelIndex}`);
- const {
- collapsedSize = 0,
- collapsible = false,
- defaultSize,
- maxSize = 100,
- minSize = 0
- } = panelConstraints;
- if (minSize > maxSize) {
- warnings.push(`min size (${minSize}%) should not be greater than max size (${maxSize}%)`);
- }
- if (defaultSize != null) {
- if (defaultSize < 0) {
- warnings.push("default size should not be less than 0");
- } else if (defaultSize < minSize && (!collapsible || defaultSize !== collapsedSize)) {
- warnings.push("default size should not be less than min size");
- }
- if (defaultSize > 100) {
- warnings.push("default size should not be greater than 100");
- } else if (defaultSize > maxSize) {
- warnings.push("default size should not be greater than max size");
- }
- }
- if (collapsedSize > minSize) {
- warnings.push("collapsed size should not be greater than min size");
- }
- if (warnings.length > 0) {
- const name = panelId != null ? `Panel "${panelId}"` : "Panel";
- console.warn(`${name} has an invalid configuration:\n\n${warnings.join("\n")}`);
- return false;
- }
- }
- return true;
- }
- // All units must be in percentages; pixel values should be pre-converted
- function validatePanelGroupLayout({
- layout: prevLayout,
- panelConstraints
- }) {
- const nextLayout = [...prevLayout];
- const nextLayoutTotalSize = nextLayout.reduce((accumulated, current) => accumulated + current, 0);
- // Validate layout expectations
- if (nextLayout.length !== panelConstraints.length) {
- throw Error(`Invalid ${panelConstraints.length} panel layout: ${nextLayout.map(size => `${size}%`).join(", ")}`);
- } else if (!fuzzyNumbersEqual(nextLayoutTotalSize, 100) && nextLayout.length > 0) {
- // This is not ideal so we should warn about it, but it may be recoverable in some cases
- // (especially if the amount is small)
- {
- console.warn(`WARNING: Invalid layout total size: ${nextLayout.map(size => `${size}%`).join(", ")}. Layout normalization will be applied.`);
- }
- for (let index = 0; index < panelConstraints.length; index++) {
- const unsafeSize = nextLayout[index];
- assert(unsafeSize != null, `No layout data found for index ${index}`);
- const safeSize = 100 / nextLayoutTotalSize * unsafeSize;
- nextLayout[index] = safeSize;
- }
- }
- let remainingSize = 0;
- // First pass: Validate the proposed layout given each panel's constraints
- for (let index = 0; index < panelConstraints.length; index++) {
- const unsafeSize = nextLayout[index];
- assert(unsafeSize != null, `No layout data found for index ${index}`);
- const safeSize = resizePanel({
- panelConstraints,
- panelIndex: index,
- size: unsafeSize
- });
- if (unsafeSize != safeSize) {
- remainingSize += unsafeSize - safeSize;
- nextLayout[index] = safeSize;
- }
- }
- // If there is additional, left over space, assign it to any panel(s) that permits it
- // (It's not worth taking multiple additional passes to evenly distribute)
- if (!fuzzyNumbersEqual(remainingSize, 0)) {
- for (let index = 0; index < panelConstraints.length; index++) {
- const prevSize = nextLayout[index];
- assert(prevSize != null, `No layout data found for index ${index}`);
- const unsafeSize = prevSize + remainingSize;
- const safeSize = resizePanel({
- panelConstraints,
- panelIndex: index,
- size: unsafeSize
- });
- if (prevSize !== safeSize) {
- remainingSize -= safeSize - prevSize;
- nextLayout[index] = safeSize;
- // Once we've used up the remainder, bail
- if (fuzzyNumbersEqual(remainingSize, 0)) {
- break;
- }
- }
- }
- }
- return nextLayout;
- }
- const LOCAL_STORAGE_DEBOUNCE_INTERVAL = 100;
- const defaultStorage = {
- getItem: name => {
- initializeDefaultStorage(defaultStorage);
- return defaultStorage.getItem(name);
- },
- setItem: (name, value) => {
- initializeDefaultStorage(defaultStorage);
- defaultStorage.setItem(name, value);
- }
- };
- const debounceMap = {};
- function PanelGroupWithForwardedRef({
- autoSaveId = null,
- children,
- className: classNameFromProps = "",
- direction,
- forwardedRef,
- id: idFromProps = null,
- onLayout = null,
- keyboardResizeBy = null,
- storage = defaultStorage,
- style: styleFromProps,
- tagName: Type = "div",
- ...rest
- }) {
- const groupId = useUniqueId(idFromProps);
- const panelGroupElementRef = useRef(null);
- const [dragState, setDragState] = useState(null);
- const [layout, setLayout] = useState([]);
- const forceUpdate = useForceUpdate();
- const panelIdToLastNotifiedSizeMapRef = useRef({});
- const panelSizeBeforeCollapseRef = useRef(new Map());
- const prevDeltaRef = useRef(0);
- const committedValuesRef = useRef({
- autoSaveId,
- direction,
- dragState,
- id: groupId,
- keyboardResizeBy,
- onLayout,
- storage
- });
- const eagerValuesRef = useRef({
- layout,
- panelDataArray: [],
- panelDataArrayChanged: false
- });
- const devWarningsRef = useRef({
- didLogIdAndOrderWarning: false,
- didLogPanelConstraintsWarning: false,
- prevPanelIds: []
- });
- useImperativeHandle(forwardedRef, () => ({
- getId: () => committedValuesRef.current.id,
- getLayout: () => {
- const {
- layout
- } = eagerValuesRef.current;
- return layout;
- },
- setLayout: unsafeLayout => {
- const {
- onLayout
- } = committedValuesRef.current;
- const {
- layout: prevLayout,
- panelDataArray
- } = eagerValuesRef.current;
- const safeLayout = validatePanelGroupLayout({
- layout: unsafeLayout,
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
- });
- if (!areEqual(prevLayout, safeLayout)) {
- setLayout(safeLayout);
- eagerValuesRef.current.layout = safeLayout;
- if (onLayout) {
- onLayout(safeLayout);
- }
- callPanelCallbacks(panelDataArray, safeLayout, panelIdToLastNotifiedSizeMapRef.current);
- }
- }
- }), []);
- useIsomorphicLayoutEffect(() => {
- committedValuesRef.current.autoSaveId = autoSaveId;
- committedValuesRef.current.direction = direction;
- committedValuesRef.current.dragState = dragState;
- committedValuesRef.current.id = groupId;
- committedValuesRef.current.onLayout = onLayout;
- committedValuesRef.current.storage = storage;
- });
- useWindowSplitterPanelGroupBehavior({
- committedValuesRef,
- eagerValuesRef,
- groupId,
- layout,
- panelDataArray: eagerValuesRef.current.panelDataArray,
- setLayout,
- panelGroupElement: panelGroupElementRef.current
- });
- useEffect(() => {
- const {
- panelDataArray
- } = eagerValuesRef.current;
- // If this panel has been configured to persist sizing information, save sizes to local storage.
- if (autoSaveId) {
- if (layout.length === 0 || layout.length !== panelDataArray.length) {
- return;
- }
- let debouncedSave = debounceMap[autoSaveId];
- // Limit the frequency of localStorage updates.
- if (debouncedSave == null) {
- debouncedSave = debounce(savePanelGroupState, LOCAL_STORAGE_DEBOUNCE_INTERVAL);
- debounceMap[autoSaveId] = debouncedSave;
- }
- // Clone mutable data before passing to the debounced function,
- // else we run the risk of saving an incorrect combination of mutable and immutable values to state.
- const clonedPanelDataArray = [...panelDataArray];
- const clonedPanelSizesBeforeCollapse = new Map(panelSizeBeforeCollapseRef.current);
- debouncedSave(autoSaveId, clonedPanelDataArray, clonedPanelSizesBeforeCollapse, layout, storage);
- }
- }, [autoSaveId, layout, storage]);
- // DEV warnings
- useEffect(() => {
- {
- const {
- panelDataArray
- } = eagerValuesRef.current;
- const {
- didLogIdAndOrderWarning,
- didLogPanelConstraintsWarning,
- prevPanelIds
- } = devWarningsRef.current;
- if (!didLogIdAndOrderWarning) {
- const panelIds = panelDataArray.map(({
- id
- }) => id);
- devWarningsRef.current.prevPanelIds = panelIds;
- const panelsHaveChanged = prevPanelIds.length > 0 && !areEqual(prevPanelIds, panelIds);
- if (panelsHaveChanged) {
- if (panelDataArray.find(({
- idIsFromProps,
- order
- }) => !idIsFromProps || order == null)) {
- devWarningsRef.current.didLogIdAndOrderWarning = true;
- console.warn(`WARNING: Panel id and order props recommended when panels are dynamically rendered`);
- }
- }
- }
- if (!didLogPanelConstraintsWarning) {
- const panelConstraints = panelDataArray.map(panelData => panelData.constraints);
- for (let panelIndex = 0; panelIndex < panelConstraints.length; panelIndex++) {
- const panelData = panelDataArray[panelIndex];
- assert(panelData, `Panel data not found for index ${panelIndex}`);
- const isValid = validatePanelConstraints({
- panelConstraints,
- panelId: panelData.id,
- panelIndex
- });
- if (!isValid) {
- devWarningsRef.current.didLogPanelConstraintsWarning = true;
- break;
- }
- }
- }
- }
- });
- // External APIs are safe to memoize via committed values ref
- const collapsePanel = useCallback(panelData => {
- const {
- onLayout
- } = committedValuesRef.current;
- const {
- layout: prevLayout,
- panelDataArray
- } = eagerValuesRef.current;
- if (panelData.constraints.collapsible) {
- const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
- const {
- collapsedSize = 0,
- panelSize,
- pivotIndices
- } = panelDataHelper(panelDataArray, panelData, prevLayout);
- assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
- if (!fuzzyNumbersEqual$1(panelSize, collapsedSize)) {
- // Store size before collapse;
- // This is the size that gets restored if the expand() API is used.
- panelSizeBeforeCollapseRef.current.set(panelData.id, panelSize);
- const isLastPanel = findPanelDataIndex(panelDataArray, panelData) === panelDataArray.length - 1;
- const delta = isLastPanel ? panelSize - collapsedSize : collapsedSize - panelSize;
- const nextLayout = adjustLayoutByDelta({
- delta,
- initialLayout: prevLayout,
- panelConstraints: panelConstraintsArray,
- pivotIndices,
- prevLayout,
- trigger: "imperative-api"
- });
- if (!compareLayouts(prevLayout, nextLayout)) {
- setLayout(nextLayout);
- eagerValuesRef.current.layout = nextLayout;
- if (onLayout) {
- onLayout(nextLayout);
- }
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
- }
- }
- }
- }, []);
- // External APIs are safe to memoize via committed values ref
- const expandPanel = useCallback((panelData, minSizeOverride) => {
- const {
- onLayout
- } = committedValuesRef.current;
- const {
- layout: prevLayout,
- panelDataArray
- } = eagerValuesRef.current;
- if (panelData.constraints.collapsible) {
- const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
- const {
- collapsedSize = 0,
- panelSize = 0,
- minSize: minSizeFromProps = 0,
- pivotIndices
- } = panelDataHelper(panelDataArray, panelData, prevLayout);
- const minSize = minSizeOverride !== null && minSizeOverride !== void 0 ? minSizeOverride : minSizeFromProps;
- if (fuzzyNumbersEqual$1(panelSize, collapsedSize)) {
- // Restore this panel to the size it was before it was collapsed, if possible.
- const prevPanelSize = panelSizeBeforeCollapseRef.current.get(panelData.id);
- const baseSize = prevPanelSize != null && prevPanelSize >= minSize ? prevPanelSize : minSize;
- const isLastPanel = findPanelDataIndex(panelDataArray, panelData) === panelDataArray.length - 1;
- const delta = isLastPanel ? panelSize - baseSize : baseSize - panelSize;
- const nextLayout = adjustLayoutByDelta({
- delta,
- initialLayout: prevLayout,
- panelConstraints: panelConstraintsArray,
- pivotIndices,
- prevLayout,
- trigger: "imperative-api"
- });
- if (!compareLayouts(prevLayout, nextLayout)) {
- setLayout(nextLayout);
- eagerValuesRef.current.layout = nextLayout;
- if (onLayout) {
- onLayout(nextLayout);
- }
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
- }
- }
- }
- }, []);
- // External APIs are safe to memoize via committed values ref
- const getPanelSize = useCallback(panelData => {
- const {
- layout,
- panelDataArray
- } = eagerValuesRef.current;
- const {
- panelSize
- } = panelDataHelper(panelDataArray, panelData, layout);
- assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
- return panelSize;
- }, []);
- // This API should never read from committedValuesRef
- const getPanelStyle = useCallback((panelData, defaultSize) => {
- const {
- panelDataArray
- } = eagerValuesRef.current;
- const panelIndex = findPanelDataIndex(panelDataArray, panelData);
- return computePanelFlexBoxStyle({
- defaultSize,
- dragState,
- layout,
- panelData: panelDataArray,
- panelIndex
- });
- }, [dragState, layout]);
- // External APIs are safe to memoize via committed values ref
- const isPanelCollapsed = useCallback(panelData => {
- const {
- layout,
- panelDataArray
- } = eagerValuesRef.current;
- const {
- collapsedSize = 0,
- collapsible,
- panelSize
- } = panelDataHelper(panelDataArray, panelData, layout);
- assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
- return collapsible === true && fuzzyNumbersEqual$1(panelSize, collapsedSize);
- }, []);
- // External APIs are safe to memoize via committed values ref
- const isPanelExpanded = useCallback(panelData => {
- const {
- layout,
- panelDataArray
- } = eagerValuesRef.current;
- const {
- collapsedSize = 0,
- collapsible,
- panelSize
- } = panelDataHelper(panelDataArray, panelData, layout);
- assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
- return !collapsible || fuzzyCompareNumbers(panelSize, collapsedSize) > 0;
- }, []);
- const registerPanel = useCallback(panelData => {
- const {
- panelDataArray
- } = eagerValuesRef.current;
- panelDataArray.push(panelData);
- panelDataArray.sort((panelA, panelB) => {
- const orderA = panelA.order;
- const orderB = panelB.order;
- if (orderA == null && orderB == null) {
- return 0;
- } else if (orderA == null) {
- return -1;
- } else if (orderB == null) {
- return 1;
- } else {
- return orderA - orderB;
- }
- });
- eagerValuesRef.current.panelDataArrayChanged = true;
- forceUpdate();
- }, [forceUpdate]);
- // (Re)calculate group layout whenever panels are registered or unregistered.
- // eslint-disable-next-line react-hooks/exhaustive-deps
- useIsomorphicLayoutEffect(() => {
- if (eagerValuesRef.current.panelDataArrayChanged) {
- eagerValuesRef.current.panelDataArrayChanged = false;
- const {
- autoSaveId,
- onLayout,
- storage
- } = committedValuesRef.current;
- const {
- layout: prevLayout,
- panelDataArray
- } = eagerValuesRef.current;
- // If this panel has been configured to persist sizing information,
- // default size should be restored from local storage if possible.
- let unsafeLayout = null;
- if (autoSaveId) {
- const state = loadPanelGroupState(autoSaveId, panelDataArray, storage);
- if (state) {
- panelSizeBeforeCollapseRef.current = new Map(Object.entries(state.expandToSizes));
- unsafeLayout = state.layout;
- }
- }
- if (unsafeLayout == null) {
- unsafeLayout = calculateUnsafeDefaultLayout({
- panelDataArray
- });
- }
- // Validate even saved layouts in case something has changed since last render
- // e.g. for pixel groups, this could be the size of the window
- const nextLayout = validatePanelGroupLayout({
- layout: unsafeLayout,
- panelConstraints: panelDataArray.map(panelData => panelData.constraints)
- });
- if (!areEqual(prevLayout, nextLayout)) {
- setLayout(nextLayout);
- eagerValuesRef.current.layout = nextLayout;
- if (onLayout) {
- onLayout(nextLayout);
- }
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
- }
- }
- });
- // Reset the cached layout if hidden by the Activity/Offscreen API
- useIsomorphicLayoutEffect(() => {
- const eagerValues = eagerValuesRef.current;
- return () => {
- eagerValues.layout = [];
- };
- }, []);
- const registerResizeHandle = useCallback(dragHandleId => {
- let isRTL = false;
- const panelGroupElement = panelGroupElementRef.current;
- if (panelGroupElement) {
- const style = window.getComputedStyle(panelGroupElement, null);
- if (style.getPropertyValue("direction") === "rtl") {
- isRTL = true;
- }
- }
- return function resizeHandler(event) {
- event.preventDefault();
- const panelGroupElement = panelGroupElementRef.current;
- if (!panelGroupElement) {
- return () => null;
- }
- const {
- direction,
- dragState,
- id: groupId,
- keyboardResizeBy,
- onLayout
- } = committedValuesRef.current;
- const {
- layout: prevLayout,
- panelDataArray
- } = eagerValuesRef.current;
- const {
- initialLayout
- } = dragState !== null && dragState !== void 0 ? dragState : {};
- const pivotIndices = determinePivotIndices(groupId, dragHandleId, panelGroupElement);
- let delta = calculateDeltaPercentage(event, dragHandleId, direction, dragState, keyboardResizeBy, panelGroupElement);
- const isHorizontal = direction === "horizontal";
- if (isHorizontal && isRTL) {
- delta = -delta;
- }
- const panelConstraints = panelDataArray.map(panelData => panelData.constraints);
- const nextLayout = adjustLayoutByDelta({
- delta,
- initialLayout: initialLayout !== null && initialLayout !== void 0 ? initialLayout : prevLayout,
- panelConstraints,
- pivotIndices,
- prevLayout,
- trigger: isKeyDown(event) ? "keyboard" : "mouse-or-touch"
- });
- const layoutChanged = !compareLayouts(prevLayout, nextLayout);
- // Only update the cursor for layout changes triggered by touch/mouse events (not keyboard)
- // Update the cursor even if the layout hasn't changed (we may need to show an invalid cursor state)
- if (isPointerEvent(event) || isMouseEvent(event)) {
- // Watch for multiple subsequent deltas; this might occur for tiny cursor movements.
- // In this case, Panel sizes might not change–
- // but updating cursor in this scenario would cause a flicker.
- if (prevDeltaRef.current != delta) {
- prevDeltaRef.current = delta;
- if (!layoutChanged && delta !== 0) {
- // If the pointer has moved too far to resize the panel any further, note this so we can update the cursor.
- // This mimics VS Code behavior.
- if (isHorizontal) {
- reportConstraintsViolation(dragHandleId, delta < 0 ? EXCEEDED_HORIZONTAL_MIN : EXCEEDED_HORIZONTAL_MAX);
- } else {
- reportConstraintsViolation(dragHandleId, delta < 0 ? EXCEEDED_VERTICAL_MIN : EXCEEDED_VERTICAL_MAX);
- }
- } else {
- reportConstraintsViolation(dragHandleId, 0);
- }
- }
- }
- if (layoutChanged) {
- setLayout(nextLayout);
- eagerValuesRef.current.layout = nextLayout;
- if (onLayout) {
- onLayout(nextLayout);
- }
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
- }
- };
- }, []);
- // External APIs are safe to memoize via committed values ref
- const resizePanel = useCallback((panelData, unsafePanelSize) => {
- const {
- onLayout
- } = committedValuesRef.current;
- const {
- layout: prevLayout,
- panelDataArray
- } = eagerValuesRef.current;
- const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
- const {
- panelSize,
- pivotIndices
- } = panelDataHelper(panelDataArray, panelData, prevLayout);
- assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
- const isLastPanel = findPanelDataIndex(panelDataArray, panelData) === panelDataArray.length - 1;
- const delta = isLastPanel ? panelSize - unsafePanelSize : unsafePanelSize - panelSize;
- const nextLayout = adjustLayoutByDelta({
- delta,
- initialLayout: prevLayout,
- panelConstraints: panelConstraintsArray,
- pivotIndices,
- prevLayout,
- trigger: "imperative-api"
- });
- if (!compareLayouts(prevLayout, nextLayout)) {
- setLayout(nextLayout);
- eagerValuesRef.current.layout = nextLayout;
- if (onLayout) {
- onLayout(nextLayout);
- }
- callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
- }
- }, []);
- const reevaluatePanelConstraints = useCallback((panelData, prevConstraints) => {
- const {
- layout,
- panelDataArray
- } = eagerValuesRef.current;
- const {
- collapsedSize: prevCollapsedSize = 0,
- collapsible: prevCollapsible
- } = prevConstraints;
- const {
- collapsedSize: nextCollapsedSize = 0,
- collapsible: nextCollapsible,
- maxSize: nextMaxSize = 100,
- minSize: nextMinSize = 0
- } = panelData.constraints;
- const {
- panelSize: prevPanelSize
- } = panelDataHelper(panelDataArray, panelData, layout);
- if (prevPanelSize == null) {
- // It's possible that the panels in this group have changed since the last render
- return;
- }
- if (prevCollapsible && nextCollapsible && fuzzyNumbersEqual$1(prevPanelSize, prevCollapsedSize)) {
- if (!fuzzyNumbersEqual$1(prevCollapsedSize, nextCollapsedSize)) {
- resizePanel(panelData, nextCollapsedSize);
- }
- } else if (prevPanelSize < nextMinSize) {
- resizePanel(panelData, nextMinSize);
- } else if (prevPanelSize > nextMaxSize) {
- resizePanel(panelData, nextMaxSize);
- }
- }, [resizePanel]);
- // TODO Multiple drag handles can be active at the same time so this API is a bit awkward now
- const startDragging = useCallback((dragHandleId, event) => {
- const {
- direction
- } = committedValuesRef.current;
- const {
- layout
- } = eagerValuesRef.current;
- if (!panelGroupElementRef.current) {
- return;
- }
- const handleElement = getResizeHandleElement(dragHandleId, panelGroupElementRef.current);
- assert(handleElement, `Drag handle element not found for id "${dragHandleId}"`);
- const initialCursorPosition = getResizeEventCursorPosition(direction, event);
- setDragState({
- dragHandleId,
- dragHandleRect: handleElement.getBoundingClientRect(),
- initialCursorPosition,
- initialLayout: layout
- });
- }, []);
- const stopDragging = useCallback(() => {
- setDragState(null);
- }, []);
- const unregisterPanel = useCallback(panelData => {
- const {
- panelDataArray
- } = eagerValuesRef.current;
- const index = findPanelDataIndex(panelDataArray, panelData);
- if (index >= 0) {
- panelDataArray.splice(index, 1);
- // TRICKY
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
- delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
- eagerValuesRef.current.panelDataArrayChanged = true;
- forceUpdate();
- }
- }, [forceUpdate]);
- const context = useMemo(() => ({
- collapsePanel,
- direction,
- dragState,
- expandPanel,
- getPanelSize,
- getPanelStyle,
- groupId,
- isPanelCollapsed,
- isPanelExpanded,
- reevaluatePanelConstraints,
- registerPanel,
- registerResizeHandle,
- resizePanel,
- startDragging,
- stopDragging,
- unregisterPanel,
- panelGroupElement: panelGroupElementRef.current
- }), [collapsePanel, dragState, direction, expandPanel, getPanelSize, getPanelStyle, groupId, isPanelCollapsed, isPanelExpanded, reevaluatePanelConstraints, registerPanel, registerResizeHandle, resizePanel, startDragging, stopDragging, unregisterPanel]);
- const style = {
- display: "flex",
- flexDirection: direction === "horizontal" ? "row" : "column",
- height: "100%",
- overflow: "hidden",
- width: "100%"
- };
- return createElement(PanelGroupContext.Provider, {
- value: context
- }, createElement(Type, {
- ...rest,
- children,
- className: classNameFromProps,
- id: idFromProps,
- ref: panelGroupElementRef,
- style: {
- ...style,
- ...styleFromProps
- },
- // CSS selectors
- [DATA_ATTRIBUTES.group]: "",
- [DATA_ATTRIBUTES.groupDirection]: direction,
- [DATA_ATTRIBUTES.groupId]: groupId
- }));
- }
- const PanelGroup = forwardRef((props, ref) => createElement(PanelGroupWithForwardedRef, {
- ...props,
- forwardedRef: ref
- }));
- PanelGroupWithForwardedRef.displayName = "PanelGroup";
- PanelGroup.displayName = "forwardRef(PanelGroup)";
- function findPanelDataIndex(panelDataArray, panelData) {
- return panelDataArray.findIndex(prevPanelData => prevPanelData === panelData || prevPanelData.id === panelData.id);
- }
- function panelDataHelper(panelDataArray, panelData, layout) {
- const panelIndex = findPanelDataIndex(panelDataArray, panelData);
- const isLastPanel = panelIndex === panelDataArray.length - 1;
- const pivotIndices = isLastPanel ? [panelIndex - 1, panelIndex] : [panelIndex, panelIndex + 1];
- const panelSize = layout[panelIndex];
- return {
- ...panelData.constraints,
- panelSize,
- pivotIndices
- };
- }
- // https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
- function useWindowSplitterResizeHandlerBehavior({
- disabled,
- handleId,
- resizeHandler,
- panelGroupElement
- }) {
- useEffect(() => {
- if (disabled || resizeHandler == null || panelGroupElement == null) {
- return;
- }
- const handleElement = getResizeHandleElement(handleId, panelGroupElement);
- if (handleElement == null) {
- return;
- }
- const onKeyDown = event => {
- if (event.defaultPrevented) {
- return;
- }
- switch (event.key) {
- case "ArrowDown":
- case "ArrowLeft":
- case "ArrowRight":
- case "ArrowUp":
- case "End":
- case "Home":
- {
- event.preventDefault();
- resizeHandler(event);
- break;
- }
- case "F6":
- {
- event.preventDefault();
- const groupId = handleElement.getAttribute(DATA_ATTRIBUTES.groupId);
- assert(groupId, `No group element found for id "${groupId}"`);
- const handles = getResizeHandleElementsForGroup(groupId, panelGroupElement);
- const index = getResizeHandleElementIndex(groupId, handleId, panelGroupElement);
- assert(index !== null, `No resize element found for id "${handleId}"`);
- const nextIndex = event.shiftKey ? index > 0 ? index - 1 : handles.length - 1 : index + 1 < handles.length ? index + 1 : 0;
- const nextHandle = handles[nextIndex];
- nextHandle.focus();
- break;
- }
- }
- };
- handleElement.addEventListener("keydown", onKeyDown);
- return () => {
- handleElement.removeEventListener("keydown", onKeyDown);
- };
- }, [panelGroupElement, disabled, handleId, resizeHandler]);
- }
- function PanelResizeHandle({
- children = null,
- className: classNameFromProps = "",
- disabled = false,
- hitAreaMargins,
- id: idFromProps,
- onBlur,
- onClick,
- onDragging,
- onFocus,
- onPointerDown,
- onPointerUp,
- style: styleFromProps = {},
- tabIndex = 0,
- tagName: Type = "div",
- ...rest
- }) {
- var _hitAreaMargins$coars, _hitAreaMargins$fine;
- const elementRef = useRef(null);
- // Use a ref to guard against users passing inline props
- const callbacksRef = useRef({
- onClick,
- onDragging,
- onPointerDown,
- onPointerUp
- });
- useEffect(() => {
- callbacksRef.current.onClick = onClick;
- callbacksRef.current.onDragging = onDragging;
- callbacksRef.current.onPointerDown = onPointerDown;
- callbacksRef.current.onPointerUp = onPointerUp;
- });
- const panelGroupContext = useContext(PanelGroupContext);
- if (panelGroupContext === null) {
- throw Error(`PanelResizeHandle components must be rendered within a PanelGroup container`);
- }
- const {
- direction,
- groupId,
- registerResizeHandle: registerResizeHandleWithParentGroup,
- startDragging,
- stopDragging,
- panelGroupElement
- } = panelGroupContext;
- const resizeHandleId = useUniqueId(idFromProps);
- const [state, setState] = useState("inactive");
- const [isFocused, setIsFocused] = useState(false);
- const [resizeHandler, setResizeHandler] = useState(null);
- const committedValuesRef = useRef({
- state
- });
- useIsomorphicLayoutEffect(() => {
- committedValuesRef.current.state = state;
- });
- useEffect(() => {
- if (disabled) {
- setResizeHandler(null);
- } else {
- const resizeHandler = registerResizeHandleWithParentGroup(resizeHandleId);
- setResizeHandler(() => resizeHandler);
- }
- }, [disabled, resizeHandleId, registerResizeHandleWithParentGroup]);
- // Extract hit area margins before passing them to the effect's dependency array
- // so that inline object values won't trigger re-renders
- const coarseHitAreaMargins = (_hitAreaMargins$coars = hitAreaMargins === null || hitAreaMargins === void 0 ? void 0 : hitAreaMargins.coarse) !== null && _hitAreaMargins$coars !== void 0 ? _hitAreaMargins$coars : 15;
- const fineHitAreaMargins = (_hitAreaMargins$fine = hitAreaMargins === null || hitAreaMargins === void 0 ? void 0 : hitAreaMargins.fine) !== null && _hitAreaMargins$fine !== void 0 ? _hitAreaMargins$fine : 5;
- useEffect(() => {
- if (disabled || resizeHandler == null) {
- return;
- }
- const element = elementRef.current;
- assert(element, "Element ref not attached");
- let didMove = false;
- const setResizeHandlerState = (action, isActive, event) => {
- if (!isActive) {
- setState("inactive");
- return;
- }
- switch (action) {
- case "down":
- {
- setState("drag");
- didMove = false;
- assert(event, 'Expected event to be defined for "down" action');
- startDragging(resizeHandleId, event);
- const {
- onDragging,
- onPointerDown
- } = callbacksRef.current;
- onDragging === null || onDragging === void 0 ? void 0 : onDragging(true);
- onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown();
- break;
- }
- case "move":
- {
- const {
- state
- } = committedValuesRef.current;
- didMove = true;
- if (state !== "drag") {
- setState("hover");
- }
- assert(event, 'Expected event to be defined for "move" action');
- resizeHandler(event);
- break;
- }
- case "up":
- {
- setState("hover");
- stopDragging();
- const {
- onClick,
- onDragging,
- onPointerUp
- } = callbacksRef.current;
- onDragging === null || onDragging === void 0 ? void 0 : onDragging(false);
- onPointerUp === null || onPointerUp === void 0 ? void 0 : onPointerUp();
- if (!didMove) {
- onClick === null || onClick === void 0 ? void 0 : onClick();
- }
- break;
- }
- }
- };
- return registerResizeHandle(resizeHandleId, element, direction, {
- coarse: coarseHitAreaMargins,
- fine: fineHitAreaMargins
- }, setResizeHandlerState);
- }, [coarseHitAreaMargins, direction, disabled, fineHitAreaMargins, registerResizeHandleWithParentGroup, resizeHandleId, resizeHandler, startDragging, stopDragging]);
- useWindowSplitterResizeHandlerBehavior({
- disabled,
- handleId: resizeHandleId,
- resizeHandler,
- panelGroupElement
- });
- const style = {
- touchAction: "none",
- userSelect: "none"
- };
- return createElement(Type, {
- ...rest,
- children,
- className: classNameFromProps,
- id: idFromProps,
- onBlur: () => {
- setIsFocused(false);
- onBlur === null || onBlur === void 0 ? void 0 : onBlur();
- },
- onFocus: () => {
- setIsFocused(true);
- onFocus === null || onFocus === void 0 ? void 0 : onFocus();
- },
- ref: elementRef,
- role: "separator",
- style: {
- ...style,
- ...styleFromProps
- },
- tabIndex,
- // CSS selectors
- [DATA_ATTRIBUTES.groupDirection]: direction,
- [DATA_ATTRIBUTES.groupId]: groupId,
- [DATA_ATTRIBUTES.resizeHandle]: "",
- [DATA_ATTRIBUTES.resizeHandleActive]: state === "drag" ? "pointer" : isFocused ? "keyboard" : undefined,
- [DATA_ATTRIBUTES.resizeHandleEnabled]: !disabled,
- [DATA_ATTRIBUTES.resizeHandleId]: resizeHandleId,
- [DATA_ATTRIBUTES.resizeHandleState]: state
- });
- }
- PanelResizeHandle.displayName = "PanelResizeHandle";
- function getPanelElement(id, scope = document) {
- const element = scope.querySelector(`[data-panel-id="${id}"]`);
- if (element) {
- return element;
- }
- return null;
- }
- function getPanelElementsForGroup(groupId, scope = document) {
- return Array.from(scope.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
- }
- function getIntersectingRectangle(rectOne, rectTwo, strict) {
- if (!intersects(rectOne, rectTwo, strict)) {
- return {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- };
- }
- return {
- x: Math.max(rectOne.x, rectTwo.x),
- y: Math.max(rectOne.y, rectTwo.y),
- width: Math.min(rectOne.x + rectOne.width, rectTwo.x + rectTwo.width) - Math.max(rectOne.x, rectTwo.x),
- height: Math.min(rectOne.y + rectOne.height, rectTwo.y + rectTwo.height) - Math.max(rectOne.y, rectTwo.y)
- };
- }
- export { DATA_ATTRIBUTES, Panel, PanelGroup, PanelResizeHandle, assert, disableGlobalCursorStyles, enableGlobalCursorStyles, getIntersectingRectangle, getPanelElement, getPanelElementsForGroup, getPanelGroupElement, getResizeHandleElement, getResizeHandleElementIndex, getResizeHandleElementsForGroup, getResizeHandlePanelIds, intersects, setNonce };
|