react-resizable-panels.browser.development.esm.js 85 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550
  1. import * as React from 'react';
  2. import { createContext, useLayoutEffect, useRef, forwardRef, createElement, useContext, useImperativeHandle, useState, useCallback, useEffect, useMemo } from 'react';
  3. // The "contextmenu" event is not supported as a PointerEvent in all browsers yet, so MouseEvent still need to be handled
  4. const PanelGroupContext = createContext(null);
  5. PanelGroupContext.displayName = "PanelGroupContext";
  6. const DATA_ATTRIBUTES = {
  7. group: "data-panel-group",
  8. groupDirection: "data-panel-group-direction",
  9. groupId: "data-panel-group-id",
  10. panel: "data-panel",
  11. panelCollapsible: "data-panel-collapsible",
  12. panelId: "data-panel-id",
  13. panelSize: "data-panel-size",
  14. resizeHandle: "data-resize-handle",
  15. resizeHandleActive: "data-resize-handle-active",
  16. resizeHandleEnabled: "data-panel-resize-handle-enabled",
  17. resizeHandleId: "data-panel-resize-handle-id",
  18. resizeHandleState: "data-resize-handle-state"
  19. };
  20. const PRECISION = 10;
  21. const useIsomorphicLayoutEffect = useLayoutEffect ;
  22. const useId = React["useId".toString()];
  23. const wrappedUseId = typeof useId === "function" ? useId : () => null;
  24. let counter = 0;
  25. function useUniqueId(idFromParams = null) {
  26. const idFromUseId = wrappedUseId();
  27. const idRef = useRef(idFromParams || idFromUseId || null);
  28. if (idRef.current === null) {
  29. idRef.current = "" + counter++;
  30. }
  31. return idFromParams !== null && idFromParams !== void 0 ? idFromParams : idRef.current;
  32. }
  33. function PanelWithForwardedRef({
  34. children,
  35. className: classNameFromProps = "",
  36. collapsedSize,
  37. collapsible,
  38. defaultSize,
  39. forwardedRef,
  40. id: idFromProps,
  41. maxSize,
  42. minSize,
  43. onCollapse,
  44. onExpand,
  45. onResize,
  46. order,
  47. style: styleFromProps,
  48. tagName: Type = "div",
  49. ...rest
  50. }) {
  51. const context = useContext(PanelGroupContext);
  52. if (context === null) {
  53. throw Error(`Panel components must be rendered within a PanelGroup container`);
  54. }
  55. const {
  56. collapsePanel,
  57. expandPanel,
  58. getPanelSize,
  59. getPanelStyle,
  60. groupId,
  61. isPanelCollapsed,
  62. reevaluatePanelConstraints,
  63. registerPanel,
  64. resizePanel,
  65. unregisterPanel
  66. } = context;
  67. const panelId = useUniqueId(idFromProps);
  68. const panelDataRef = useRef({
  69. callbacks: {
  70. onCollapse,
  71. onExpand,
  72. onResize
  73. },
  74. constraints: {
  75. collapsedSize,
  76. collapsible,
  77. defaultSize,
  78. maxSize,
  79. minSize
  80. },
  81. id: panelId,
  82. idIsFromProps: idFromProps !== undefined,
  83. order
  84. });
  85. const devWarningsRef = useRef({
  86. didLogMissingDefaultSizeWarning: false
  87. });
  88. // Normally we wouldn't log a warning during render,
  89. // but effects don't run on the server, so we can't do it there
  90. {
  91. if (!devWarningsRef.current.didLogMissingDefaultSizeWarning) ;
  92. }
  93. useIsomorphicLayoutEffect(() => {
  94. const {
  95. callbacks,
  96. constraints
  97. } = panelDataRef.current;
  98. const prevConstraints = {
  99. ...constraints
  100. };
  101. panelDataRef.current.id = panelId;
  102. panelDataRef.current.idIsFromProps = idFromProps !== undefined;
  103. panelDataRef.current.order = order;
  104. callbacks.onCollapse = onCollapse;
  105. callbacks.onExpand = onExpand;
  106. callbacks.onResize = onResize;
  107. constraints.collapsedSize = collapsedSize;
  108. constraints.collapsible = collapsible;
  109. constraints.defaultSize = defaultSize;
  110. constraints.maxSize = maxSize;
  111. constraints.minSize = minSize;
  112. // If constraints have changed, we should revisit panel sizes.
  113. // This is uncommon but may happen if people are trying to implement pixel based constraints.
  114. if (prevConstraints.collapsedSize !== constraints.collapsedSize || prevConstraints.collapsible !== constraints.collapsible || prevConstraints.maxSize !== constraints.maxSize || prevConstraints.minSize !== constraints.minSize) {
  115. reevaluatePanelConstraints(panelDataRef.current, prevConstraints);
  116. }
  117. });
  118. useIsomorphicLayoutEffect(() => {
  119. const panelData = panelDataRef.current;
  120. registerPanel(panelData);
  121. return () => {
  122. unregisterPanel(panelData);
  123. };
  124. }, [order, panelId, registerPanel, unregisterPanel]);
  125. useImperativeHandle(forwardedRef, () => ({
  126. collapse: () => {
  127. collapsePanel(panelDataRef.current);
  128. },
  129. expand: minSize => {
  130. expandPanel(panelDataRef.current, minSize);
  131. },
  132. getId() {
  133. return panelId;
  134. },
  135. getSize() {
  136. return getPanelSize(panelDataRef.current);
  137. },
  138. isCollapsed() {
  139. return isPanelCollapsed(panelDataRef.current);
  140. },
  141. isExpanded() {
  142. return !isPanelCollapsed(panelDataRef.current);
  143. },
  144. resize: size => {
  145. resizePanel(panelDataRef.current, size);
  146. }
  147. }), [collapsePanel, expandPanel, getPanelSize, isPanelCollapsed, panelId, resizePanel]);
  148. const style = getPanelStyle(panelDataRef.current, defaultSize);
  149. return createElement(Type, {
  150. ...rest,
  151. children,
  152. className: classNameFromProps,
  153. id: panelId,
  154. style: {
  155. ...style,
  156. ...styleFromProps
  157. },
  158. // CSS selectors
  159. [DATA_ATTRIBUTES.groupId]: groupId,
  160. [DATA_ATTRIBUTES.panel]: "",
  161. [DATA_ATTRIBUTES.panelCollapsible]: collapsible || undefined,
  162. [DATA_ATTRIBUTES.panelId]: panelId,
  163. [DATA_ATTRIBUTES.panelSize]: parseFloat("" + style.flexGrow).toFixed(1)
  164. });
  165. }
  166. const Panel = forwardRef((props, ref) => createElement(PanelWithForwardedRef, {
  167. ...props,
  168. forwardedRef: ref
  169. }));
  170. PanelWithForwardedRef.displayName = "Panel";
  171. Panel.displayName = "forwardRef(Panel)";
  172. let nonce;
  173. function getNonce() {
  174. return nonce;
  175. }
  176. function setNonce(value) {
  177. nonce = value;
  178. }
  179. let currentCursorStyle = null;
  180. let enabled = true;
  181. let prevRuleIndex = -1;
  182. let styleElement = null;
  183. function disableGlobalCursorStyles() {
  184. enabled = false;
  185. }
  186. function enableGlobalCursorStyles() {
  187. enabled = true;
  188. }
  189. function getCursorStyle(state, constraintFlags) {
  190. if (constraintFlags) {
  191. const horizontalMin = (constraintFlags & EXCEEDED_HORIZONTAL_MIN) !== 0;
  192. const horizontalMax = (constraintFlags & EXCEEDED_HORIZONTAL_MAX) !== 0;
  193. const verticalMin = (constraintFlags & EXCEEDED_VERTICAL_MIN) !== 0;
  194. const verticalMax = (constraintFlags & EXCEEDED_VERTICAL_MAX) !== 0;
  195. if (horizontalMin) {
  196. if (verticalMin) {
  197. return "se-resize";
  198. } else if (verticalMax) {
  199. return "ne-resize";
  200. } else {
  201. return "e-resize";
  202. }
  203. } else if (horizontalMax) {
  204. if (verticalMin) {
  205. return "sw-resize";
  206. } else if (verticalMax) {
  207. return "nw-resize";
  208. } else {
  209. return "w-resize";
  210. }
  211. } else if (verticalMin) {
  212. return "s-resize";
  213. } else if (verticalMax) {
  214. return "n-resize";
  215. }
  216. }
  217. switch (state) {
  218. case "horizontal":
  219. return "ew-resize";
  220. case "intersection":
  221. return "move";
  222. case "vertical":
  223. return "ns-resize";
  224. }
  225. }
  226. function resetGlobalCursorStyle() {
  227. if (styleElement !== null) {
  228. document.head.removeChild(styleElement);
  229. currentCursorStyle = null;
  230. styleElement = null;
  231. prevRuleIndex = -1;
  232. }
  233. }
  234. function setGlobalCursorStyle(state, constraintFlags) {
  235. var _styleElement$sheet$i, _styleElement$sheet2;
  236. if (!enabled) {
  237. return;
  238. }
  239. const style = getCursorStyle(state, constraintFlags);
  240. if (currentCursorStyle === style) {
  241. return;
  242. }
  243. currentCursorStyle = style;
  244. if (styleElement === null) {
  245. styleElement = document.createElement("style");
  246. const nonce = getNonce();
  247. if (nonce) {
  248. styleElement.setAttribute("nonce", nonce);
  249. }
  250. document.head.appendChild(styleElement);
  251. }
  252. if (prevRuleIndex >= 0) {
  253. var _styleElement$sheet;
  254. (_styleElement$sheet = styleElement.sheet) === null || _styleElement$sheet === void 0 ? void 0 : _styleElement$sheet.removeRule(prevRuleIndex);
  255. }
  256. 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;
  257. }
  258. function isKeyDown(event) {
  259. return event.type === "keydown";
  260. }
  261. function isPointerEvent(event) {
  262. return event.type.startsWith("pointer");
  263. }
  264. function isMouseEvent(event) {
  265. return event.type.startsWith("mouse");
  266. }
  267. function getResizeEventCoordinates(event) {
  268. if (isPointerEvent(event)) {
  269. if (event.isPrimary) {
  270. return {
  271. x: event.clientX,
  272. y: event.clientY
  273. };
  274. }
  275. } else if (isMouseEvent(event)) {
  276. return {
  277. x: event.clientX,
  278. y: event.clientY
  279. };
  280. }
  281. return {
  282. x: Infinity,
  283. y: Infinity
  284. };
  285. }
  286. function getInputType() {
  287. if (typeof matchMedia === "function") {
  288. return matchMedia("(pointer:coarse)").matches ? "coarse" : "fine";
  289. }
  290. }
  291. function intersects(rectOne, rectTwo, strict) {
  292. if (strict) {
  293. 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;
  294. } else {
  295. 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;
  296. }
  297. }
  298. // Forked from NPM stacking-order@2.0.0
  299. /**
  300. * Determine which of two nodes appears in front of the other —
  301. * if `a` is in front, returns 1, otherwise returns -1
  302. * @param {HTMLElement | SVGElement} a
  303. * @param {HTMLElement | SVGElement} b
  304. */
  305. function compare(a, b) {
  306. if (a === b) throw new Error("Cannot compare node with itself");
  307. const ancestors = {
  308. a: get_ancestors(a),
  309. b: get_ancestors(b)
  310. };
  311. let common_ancestor;
  312. // remove shared ancestors
  313. while (ancestors.a.at(-1) === ancestors.b.at(-1)) {
  314. a = ancestors.a.pop();
  315. b = ancestors.b.pop();
  316. common_ancestor = a;
  317. }
  318. assert(common_ancestor, "Stacking order can only be calculated for elements with a common ancestor");
  319. const z_indexes = {
  320. a: get_z_index(find_stacking_context(ancestors.a)),
  321. b: get_z_index(find_stacking_context(ancestors.b))
  322. };
  323. if (z_indexes.a === z_indexes.b) {
  324. const children = common_ancestor.childNodes;
  325. const furthest_ancestors = {
  326. a: ancestors.a.at(-1),
  327. b: ancestors.b.at(-1)
  328. };
  329. let i = children.length;
  330. while (i--) {
  331. const child = children[i];
  332. if (child === furthest_ancestors.a) return 1;
  333. if (child === furthest_ancestors.b) return -1;
  334. }
  335. }
  336. return Math.sign(z_indexes.a - z_indexes.b);
  337. }
  338. const props = /\b(?:position|zIndex|opacity|transform|webkitTransform|mixBlendMode|filter|webkitFilter|isolation)\b/;
  339. /** @param {HTMLElement | SVGElement} node */
  340. function is_flex_item(node) {
  341. var _get_parent;
  342. // @ts-ignore
  343. const display = getComputedStyle((_get_parent = get_parent(node)) !== null && _get_parent !== void 0 ? _get_parent : node).display;
  344. return display === "flex" || display === "inline-flex";
  345. }
  346. /** @param {HTMLElement | SVGElement} node */
  347. function creates_stacking_context(node) {
  348. const style = getComputedStyle(node);
  349. // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
  350. if (style.position === "fixed") return true;
  351. // Forked to fix upstream bug https://github.com/Rich-Harris/stacking-order/issues/3
  352. // if (
  353. // (style.zIndex !== "auto" && style.position !== "static") ||
  354. // is_flex_item(node)
  355. // )
  356. if (style.zIndex !== "auto" && (style.position !== "static" || is_flex_item(node))) return true;
  357. if (+style.opacity < 1) return true;
  358. if ("transform" in style && style.transform !== "none") return true;
  359. if ("webkitTransform" in style && style.webkitTransform !== "none") return true;
  360. if ("mixBlendMode" in style && style.mixBlendMode !== "normal") return true;
  361. if ("filter" in style && style.filter !== "none") return true;
  362. if ("webkitFilter" in style && style.webkitFilter !== "none") return true;
  363. if ("isolation" in style && style.isolation === "isolate") return true;
  364. if (props.test(style.willChange)) return true;
  365. // @ts-expect-error
  366. if (style.webkitOverflowScrolling === "touch") return true;
  367. return false;
  368. }
  369. /** @param {(HTMLElement| SVGElement)[]} nodes */
  370. function find_stacking_context(nodes) {
  371. let i = nodes.length;
  372. while (i--) {
  373. const node = nodes[i];
  374. assert(node, "Missing node");
  375. if (creates_stacking_context(node)) return node;
  376. }
  377. return null;
  378. }
  379. /** @param {HTMLElement | SVGElement} node */
  380. function get_z_index(node) {
  381. return node && Number(getComputedStyle(node).zIndex) || 0;
  382. }
  383. /** @param {HTMLElement} node */
  384. function get_ancestors(node) {
  385. const ancestors = [];
  386. while (node) {
  387. ancestors.push(node);
  388. // @ts-ignore
  389. node = get_parent(node);
  390. }
  391. return ancestors; // [ node, ... <body>, <html>, document ]
  392. }
  393. /** @param {HTMLElement} node */
  394. function get_parent(node) {
  395. const {
  396. parentNode
  397. } = node;
  398. if (parentNode && parentNode instanceof ShadowRoot) {
  399. return parentNode.host;
  400. }
  401. return parentNode;
  402. }
  403. const EXCEEDED_HORIZONTAL_MIN = 0b0001;
  404. const EXCEEDED_HORIZONTAL_MAX = 0b0010;
  405. const EXCEEDED_VERTICAL_MIN = 0b0100;
  406. const EXCEEDED_VERTICAL_MAX = 0b1000;
  407. const isCoarsePointer = getInputType() === "coarse";
  408. let intersectingHandles = [];
  409. let isPointerDown = false;
  410. let ownerDocumentCounts = new Map();
  411. let panelConstraintFlags = new Map();
  412. const registeredResizeHandlers = new Set();
  413. function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins, setResizeHandlerState) {
  414. var _ownerDocumentCounts$;
  415. const {
  416. ownerDocument
  417. } = element;
  418. const data = {
  419. direction,
  420. element,
  421. hitAreaMargins,
  422. setResizeHandlerState
  423. };
  424. const count = (_ownerDocumentCounts$ = ownerDocumentCounts.get(ownerDocument)) !== null && _ownerDocumentCounts$ !== void 0 ? _ownerDocumentCounts$ : 0;
  425. ownerDocumentCounts.set(ownerDocument, count + 1);
  426. registeredResizeHandlers.add(data);
  427. updateListeners();
  428. return function unregisterResizeHandle() {
  429. var _ownerDocumentCounts$2;
  430. panelConstraintFlags.delete(resizeHandleId);
  431. registeredResizeHandlers.delete(data);
  432. const count = (_ownerDocumentCounts$2 = ownerDocumentCounts.get(ownerDocument)) !== null && _ownerDocumentCounts$2 !== void 0 ? _ownerDocumentCounts$2 : 1;
  433. ownerDocumentCounts.set(ownerDocument, count - 1);
  434. updateListeners();
  435. if (count === 1) {
  436. ownerDocumentCounts.delete(ownerDocument);
  437. }
  438. // If the resize handle that is currently unmounting is intersecting with the pointer,
  439. // update the global pointer to account for the change
  440. if (intersectingHandles.includes(data)) {
  441. const index = intersectingHandles.indexOf(data);
  442. if (index >= 0) {
  443. intersectingHandles.splice(index, 1);
  444. }
  445. updateCursor();
  446. // Also instruct the handle to stop dragging; this prevents the parent group from being left in an inconsistent state
  447. // See github.com/bvaughn/react-resizable-panels/issues/402
  448. setResizeHandlerState("up", true, null);
  449. }
  450. };
  451. }
  452. function handlePointerDown(event) {
  453. const {
  454. target
  455. } = event;
  456. const {
  457. x,
  458. y
  459. } = getResizeEventCoordinates(event);
  460. isPointerDown = true;
  461. recalculateIntersectingHandles({
  462. target,
  463. x,
  464. y
  465. });
  466. updateListeners();
  467. if (intersectingHandles.length > 0) {
  468. updateResizeHandlerStates("down", event);
  469. event.preventDefault();
  470. if (!isWithinResizeHandle(target)) {
  471. event.stopImmediatePropagation();
  472. }
  473. }
  474. }
  475. function handlePointerMove(event) {
  476. const {
  477. x,
  478. y
  479. } = getResizeEventCoordinates(event);
  480. // Edge case (see #340)
  481. // Detect when the pointer has been released outside an iframe on a different domain
  482. if (isPointerDown && event.buttons === 0) {
  483. isPointerDown = false;
  484. updateResizeHandlerStates("up", event);
  485. }
  486. if (!isPointerDown) {
  487. const {
  488. target
  489. } = event;
  490. // Recalculate intersecting handles whenever the pointer moves, except if it has already been pressed
  491. // at that point, the handles may not move with the pointer (depending on constraints)
  492. // but the same set of active handles should be locked until the pointer is released
  493. recalculateIntersectingHandles({
  494. target,
  495. x,
  496. y
  497. });
  498. }
  499. updateResizeHandlerStates("move", event);
  500. // Update cursor based on return value(s) from active handles
  501. updateCursor();
  502. if (intersectingHandles.length > 0) {
  503. event.preventDefault();
  504. }
  505. }
  506. function handlePointerUp(event) {
  507. const {
  508. target
  509. } = event;
  510. const {
  511. x,
  512. y
  513. } = getResizeEventCoordinates(event);
  514. panelConstraintFlags.clear();
  515. isPointerDown = false;
  516. if (intersectingHandles.length > 0) {
  517. event.preventDefault();
  518. if (!isWithinResizeHandle(target)) {
  519. event.stopImmediatePropagation();
  520. }
  521. }
  522. updateResizeHandlerStates("up", event);
  523. recalculateIntersectingHandles({
  524. target,
  525. x,
  526. y
  527. });
  528. updateCursor();
  529. updateListeners();
  530. }
  531. function isWithinResizeHandle(element) {
  532. let currentElement = element;
  533. while (currentElement) {
  534. if (currentElement.hasAttribute(DATA_ATTRIBUTES.resizeHandle)) {
  535. return true;
  536. }
  537. currentElement = currentElement.parentElement;
  538. }
  539. return false;
  540. }
  541. function recalculateIntersectingHandles({
  542. target,
  543. x,
  544. y
  545. }) {
  546. intersectingHandles.splice(0);
  547. let targetElement = null;
  548. if (target instanceof HTMLElement || target instanceof SVGElement) {
  549. targetElement = target;
  550. }
  551. registeredResizeHandlers.forEach(data => {
  552. const {
  553. element: dragHandleElement,
  554. hitAreaMargins
  555. } = data;
  556. const dragHandleRect = dragHandleElement.getBoundingClientRect();
  557. const {
  558. bottom,
  559. left,
  560. right,
  561. top
  562. } = dragHandleRect;
  563. const margin = isCoarsePointer ? hitAreaMargins.coarse : hitAreaMargins.fine;
  564. const eventIntersects = x >= left - margin && x <= right + margin && y >= top - margin && y <= bottom + margin;
  565. if (eventIntersects) {
  566. // TRICKY
  567. // We listen for pointers events at the root in order to support hit area margins
  568. // (determining when the pointer is close enough to an element to be considered a "hit")
  569. // Clicking on an element "above" a handle (e.g. a modal) should prevent a hit though
  570. // so at this point we need to compare stacking order of a potentially intersecting drag handle,
  571. // and the element that was actually clicked/touched
  572. if (targetElement !== null && document.contains(targetElement) && dragHandleElement !== targetElement && !dragHandleElement.contains(targetElement) && !targetElement.contains(dragHandleElement) &&
  573. // Calculating stacking order has a cost, so we should avoid it if possible
  574. // That is why we only check potentially intersecting handles,
  575. // and why we skip if the event target is within the handle's DOM
  576. compare(targetElement, dragHandleElement) > 0) {
  577. // If the target is above the drag handle, then we also need to confirm they overlap
  578. // If they are beside each other (e.g. a panel and its drag handle) then the handle is still interactive
  579. //
  580. // It's not enough to compare only the target
  581. // The target might be a small element inside of a larger container
  582. // (For example, a SPAN or a DIV inside of a larger modal dialog)
  583. let currentElement = targetElement;
  584. let didIntersect = false;
  585. while (currentElement) {
  586. if (currentElement.contains(dragHandleElement)) {
  587. break;
  588. } else if (intersects(currentElement.getBoundingClientRect(), dragHandleRect, true)) {
  589. didIntersect = true;
  590. break;
  591. }
  592. currentElement = currentElement.parentElement;
  593. }
  594. if (didIntersect) {
  595. return;
  596. }
  597. }
  598. intersectingHandles.push(data);
  599. }
  600. });
  601. }
  602. function reportConstraintsViolation(resizeHandleId, flag) {
  603. panelConstraintFlags.set(resizeHandleId, flag);
  604. }
  605. function updateCursor() {
  606. let intersectsHorizontal = false;
  607. let intersectsVertical = false;
  608. intersectingHandles.forEach(data => {
  609. const {
  610. direction
  611. } = data;
  612. if (direction === "horizontal") {
  613. intersectsHorizontal = true;
  614. } else {
  615. intersectsVertical = true;
  616. }
  617. });
  618. let constraintFlags = 0;
  619. panelConstraintFlags.forEach(flag => {
  620. constraintFlags |= flag;
  621. });
  622. if (intersectsHorizontal && intersectsVertical) {
  623. setGlobalCursorStyle("intersection", constraintFlags);
  624. } else if (intersectsHorizontal) {
  625. setGlobalCursorStyle("horizontal", constraintFlags);
  626. } else if (intersectsVertical) {
  627. setGlobalCursorStyle("vertical", constraintFlags);
  628. } else {
  629. resetGlobalCursorStyle();
  630. }
  631. }
  632. let listenersAbortController = new AbortController();
  633. function updateListeners() {
  634. listenersAbortController.abort();
  635. listenersAbortController = new AbortController();
  636. const options = {
  637. capture: true,
  638. signal: listenersAbortController.signal
  639. };
  640. if (!registeredResizeHandlers.size) {
  641. return;
  642. }
  643. if (isPointerDown) {
  644. if (intersectingHandles.length > 0) {
  645. ownerDocumentCounts.forEach((count, ownerDocument) => {
  646. const {
  647. body
  648. } = ownerDocument;
  649. if (count > 0) {
  650. body.addEventListener("contextmenu", handlePointerUp, options);
  651. body.addEventListener("pointerleave", handlePointerMove, options);
  652. body.addEventListener("pointermove", handlePointerMove, options);
  653. }
  654. });
  655. }
  656. window.addEventListener("pointerup", handlePointerUp, options);
  657. window.addEventListener("pointercancel", handlePointerUp, options);
  658. } else {
  659. ownerDocumentCounts.forEach((count, ownerDocument) => {
  660. const {
  661. body
  662. } = ownerDocument;
  663. if (count > 0) {
  664. body.addEventListener("pointerdown", handlePointerDown, options);
  665. body.addEventListener("pointermove", handlePointerMove, options);
  666. }
  667. });
  668. }
  669. }
  670. function updateResizeHandlerStates(action, event) {
  671. registeredResizeHandlers.forEach(data => {
  672. const {
  673. setResizeHandlerState
  674. } = data;
  675. const isActive = intersectingHandles.includes(data);
  676. setResizeHandlerState(action, isActive, event);
  677. });
  678. }
  679. function useForceUpdate() {
  680. const [_, setCount] = useState(0);
  681. return useCallback(() => setCount(prevCount => prevCount + 1), []);
  682. }
  683. function assert(expectedCondition, message) {
  684. if (!expectedCondition) {
  685. console.error(message);
  686. throw Error(message);
  687. }
  688. }
  689. function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
  690. if (actual.toFixed(fractionDigits) === expected.toFixed(fractionDigits)) {
  691. return 0;
  692. } else {
  693. return actual > expected ? 1 : -1;
  694. }
  695. }
  696. function fuzzyNumbersEqual$1(actual, expected, fractionDigits = PRECISION) {
  697. return fuzzyCompareNumbers(actual, expected, fractionDigits) === 0;
  698. }
  699. function fuzzyNumbersEqual(actual, expected, fractionDigits) {
  700. return fuzzyCompareNumbers(actual, expected, fractionDigits) === 0;
  701. }
  702. function fuzzyLayoutsEqual(actual, expected, fractionDigits) {
  703. if (actual.length !== expected.length) {
  704. return false;
  705. }
  706. for (let index = 0; index < actual.length; index++) {
  707. const actualSize = actual[index];
  708. const expectedSize = expected[index];
  709. if (!fuzzyNumbersEqual(actualSize, expectedSize, fractionDigits)) {
  710. return false;
  711. }
  712. }
  713. return true;
  714. }
  715. // Panel size must be in percentages; pixel values should be pre-converted
  716. function resizePanel({
  717. panelConstraints: panelConstraintsArray,
  718. panelIndex,
  719. size
  720. }) {
  721. const panelConstraints = panelConstraintsArray[panelIndex];
  722. assert(panelConstraints != null, `Panel constraints not found for index ${panelIndex}`);
  723. let {
  724. collapsedSize = 0,
  725. collapsible,
  726. maxSize = 100,
  727. minSize = 0
  728. } = panelConstraints;
  729. if (fuzzyCompareNumbers(size, minSize) < 0) {
  730. if (collapsible) {
  731. // Collapsible panels should snap closed or open only once they cross the halfway point between collapsed and min size.
  732. const halfwayPoint = (collapsedSize + minSize) / 2;
  733. if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
  734. size = collapsedSize;
  735. } else {
  736. size = minSize;
  737. }
  738. } else {
  739. size = minSize;
  740. }
  741. }
  742. size = Math.min(maxSize, size);
  743. size = parseFloat(size.toFixed(PRECISION));
  744. return size;
  745. }
  746. // All units must be in percentages; pixel values should be pre-converted
  747. function adjustLayoutByDelta({
  748. delta,
  749. initialLayout,
  750. panelConstraints: panelConstraintsArray,
  751. pivotIndices,
  752. prevLayout,
  753. trigger
  754. }) {
  755. if (fuzzyNumbersEqual(delta, 0)) {
  756. return initialLayout;
  757. }
  758. const nextLayout = [...initialLayout];
  759. const [firstPivotIndex, secondPivotIndex] = pivotIndices;
  760. assert(firstPivotIndex != null, "Invalid first pivot index");
  761. assert(secondPivotIndex != null, "Invalid second pivot index");
  762. let deltaApplied = 0;
  763. // const DEBUG = [];
  764. // DEBUG.push(`adjustLayoutByDelta()`);
  765. // DEBUG.push(` initialLayout: ${initialLayout.join(", ")}`);
  766. // DEBUG.push(` prevLayout: ${prevLayout.join(", ")}`);
  767. // DEBUG.push(` delta: ${delta}`);
  768. // DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
  769. // DEBUG.push(` trigger: ${trigger}`);
  770. // DEBUG.push("");
  771. // A resizing panel affects the panels before or after it.
  772. //
  773. // A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
  774. // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
  775. //
  776. // A positive delta means the panel(s) immediately before the resize handle should "expand".
  777. // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
  778. {
  779. // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
  780. // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
  781. if (trigger === "keyboard") {
  782. {
  783. // Check if we should expand a collapsed panel
  784. const index = delta < 0 ? secondPivotIndex : firstPivotIndex;
  785. const panelConstraints = panelConstraintsArray[index];
  786. assert(panelConstraints, `Panel constraints not found for index ${index}`);
  787. const {
  788. collapsedSize = 0,
  789. collapsible,
  790. minSize = 0
  791. } = panelConstraints;
  792. // DEBUG.push(`edge case check 1: ${index}`);
  793. // DEBUG.push(` -> collapsible? ${collapsible}`);
  794. if (collapsible) {
  795. const prevSize = initialLayout[index];
  796. assert(prevSize != null, `Previous layout not found for panel index ${index}`);
  797. if (fuzzyNumbersEqual(prevSize, collapsedSize)) {
  798. const localDelta = minSize - prevSize;
  799. // DEBUG.push(` -> expand delta: ${localDelta}`);
  800. if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
  801. delta = delta < 0 ? 0 - localDelta : localDelta;
  802. // DEBUG.push(` -> delta: ${delta}`);
  803. }
  804. }
  805. }
  806. }
  807. {
  808. // Check if we should collapse a panel at its minimum size
  809. const index = delta < 0 ? firstPivotIndex : secondPivotIndex;
  810. const panelConstraints = panelConstraintsArray[index];
  811. assert(panelConstraints, `No panel constraints found for index ${index}`);
  812. const {
  813. collapsedSize = 0,
  814. collapsible,
  815. minSize = 0
  816. } = panelConstraints;
  817. // DEBUG.push(`edge case check 2: ${index}`);
  818. // DEBUG.push(` -> collapsible? ${collapsible}`);
  819. if (collapsible) {
  820. const prevSize = initialLayout[index];
  821. assert(prevSize != null, `Previous layout not found for panel index ${index}`);
  822. if (fuzzyNumbersEqual(prevSize, minSize)) {
  823. const localDelta = prevSize - collapsedSize;
  824. // DEBUG.push(` -> expand delta: ${localDelta}`);
  825. if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
  826. delta = delta < 0 ? 0 - localDelta : localDelta;
  827. // DEBUG.push(` -> delta: ${delta}`);
  828. }
  829. }
  830. }
  831. }
  832. }
  833. // DEBUG.push("");
  834. }
  835. {
  836. // Pre-calculate max available delta in the opposite direction of our pivot.
  837. // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
  838. // If this amount is less than the requested delta, adjust the requested delta.
  839. // If this amount is greater than the requested delta, that's useful information too–
  840. // as an expanding panel might change from collapsed to min size.
  841. const increment = delta < 0 ? 1 : -1;
  842. let index = delta < 0 ? secondPivotIndex : firstPivotIndex;
  843. let maxAvailableDelta = 0;
  844. // DEBUG.push("pre calc...");
  845. while (true) {
  846. const prevSize = initialLayout[index];
  847. assert(prevSize != null, `Previous layout not found for panel index ${index}`);
  848. const maxSafeSize = resizePanel({
  849. panelConstraints: panelConstraintsArray,
  850. panelIndex: index,
  851. size: 100
  852. });
  853. const delta = maxSafeSize - prevSize;
  854. // DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
  855. maxAvailableDelta += delta;
  856. index += increment;
  857. if (index < 0 || index >= panelConstraintsArray.length) {
  858. break;
  859. }
  860. }
  861. // DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
  862. const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
  863. delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
  864. // DEBUG.push(` -> adjusted delta: ${delta}`);
  865. // DEBUG.push("");
  866. }
  867. {
  868. // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
  869. const pivotIndex = delta < 0 ? firstPivotIndex : secondPivotIndex;
  870. let index = pivotIndex;
  871. while (index >= 0 && index < panelConstraintsArray.length) {
  872. const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
  873. const prevSize = initialLayout[index];
  874. assert(prevSize != null, `Previous layout not found for panel index ${index}`);
  875. const unsafeSize = prevSize - deltaRemaining;
  876. const safeSize = resizePanel({
  877. panelConstraints: panelConstraintsArray,
  878. panelIndex: index,
  879. size: unsafeSize
  880. });
  881. if (!fuzzyNumbersEqual(prevSize, safeSize)) {
  882. deltaApplied += prevSize - safeSize;
  883. nextLayout[index] = safeSize;
  884. if (deltaApplied.toPrecision(3).localeCompare(Math.abs(delta).toPrecision(3), undefined, {
  885. numeric: true
  886. }) >= 0) {
  887. break;
  888. }
  889. }
  890. if (delta < 0) {
  891. index--;
  892. } else {
  893. index++;
  894. }
  895. }
  896. }
  897. // DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
  898. // DEBUG.push(` deltaApplied: ${deltaApplied}`);
  899. // DEBUG.push("");
  900. // If we were unable to resize any of the panels panels, return the previous state.
  901. // This will essentially bailout and ignore e.g. drags past a panel's boundaries
  902. if (fuzzyLayoutsEqual(prevLayout, nextLayout)) {
  903. // DEBUG.push(`bailout to previous layout: ${prevLayout.join(", ")}`);
  904. // console.log(DEBUG.join("\n"));
  905. return prevLayout;
  906. }
  907. {
  908. // Now distribute the applied delta to the panels in the other direction
  909. const pivotIndex = delta < 0 ? secondPivotIndex : firstPivotIndex;
  910. const prevSize = initialLayout[pivotIndex];
  911. assert(prevSize != null, `Previous layout not found for panel index ${pivotIndex}`);
  912. const unsafeSize = prevSize + deltaApplied;
  913. const safeSize = resizePanel({
  914. panelConstraints: panelConstraintsArray,
  915. panelIndex: pivotIndex,
  916. size: unsafeSize
  917. });
  918. // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
  919. nextLayout[pivotIndex] = safeSize;
  920. // Edge case where expanding or contracting one panel caused another one to change collapsed state
  921. if (!fuzzyNumbersEqual(safeSize, unsafeSize)) {
  922. let deltaRemaining = unsafeSize - safeSize;
  923. const pivotIndex = delta < 0 ? secondPivotIndex : firstPivotIndex;
  924. let index = pivotIndex;
  925. while (index >= 0 && index < panelConstraintsArray.length) {
  926. const prevSize = nextLayout[index];
  927. assert(prevSize != null, `Previous layout not found for panel index ${index}`);
  928. const unsafeSize = prevSize + deltaRemaining;
  929. const safeSize = resizePanel({
  930. panelConstraints: panelConstraintsArray,
  931. panelIndex: index,
  932. size: unsafeSize
  933. });
  934. if (!fuzzyNumbersEqual(prevSize, safeSize)) {
  935. deltaRemaining -= safeSize - prevSize;
  936. nextLayout[index] = safeSize;
  937. }
  938. if (fuzzyNumbersEqual(deltaRemaining, 0)) {
  939. break;
  940. }
  941. if (delta > 0) {
  942. index--;
  943. } else {
  944. index++;
  945. }
  946. }
  947. }
  948. }
  949. // DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
  950. // DEBUG.push(` deltaApplied: ${deltaApplied}`);
  951. // DEBUG.push("");
  952. const totalSize = nextLayout.reduce((total, size) => size + total, 0);
  953. // DEBUG.push(`total size: ${totalSize}`);
  954. // If our new layout doesn't add up to 100%, that means the requested delta can't be applied
  955. // In that case, fall back to our most recent valid layout
  956. if (!fuzzyNumbersEqual(totalSize, 100)) {
  957. // DEBUG.push(`bailout to previous layout: ${prevLayout.join(", ")}`);
  958. // console.log(DEBUG.join("\n"));
  959. return prevLayout;
  960. }
  961. // console.log(DEBUG.join("\n"));
  962. return nextLayout;
  963. }
  964. function calculateAriaValues({
  965. layout,
  966. panelsArray,
  967. pivotIndices
  968. }) {
  969. let currentMinSize = 0;
  970. let currentMaxSize = 100;
  971. let totalMinSize = 0;
  972. let totalMaxSize = 0;
  973. const firstIndex = pivotIndices[0];
  974. assert(firstIndex != null, "No pivot index found");
  975. // A panel's effective min/max sizes also need to account for other panel's sizes.
  976. panelsArray.forEach((panelData, index) => {
  977. const {
  978. constraints
  979. } = panelData;
  980. const {
  981. maxSize = 100,
  982. minSize = 0
  983. } = constraints;
  984. if (index === firstIndex) {
  985. currentMinSize = minSize;
  986. currentMaxSize = maxSize;
  987. } else {
  988. totalMinSize += minSize;
  989. totalMaxSize += maxSize;
  990. }
  991. });
  992. const valueMax = Math.min(currentMaxSize, 100 - totalMinSize);
  993. const valueMin = Math.max(currentMinSize, 100 - totalMaxSize);
  994. const valueNow = layout[firstIndex];
  995. return {
  996. valueMax,
  997. valueMin,
  998. valueNow
  999. };
  1000. }
  1001. function getResizeHandleElementsForGroup(groupId, scope = document) {
  1002. return Array.from(scope.querySelectorAll(`[${DATA_ATTRIBUTES.resizeHandleId}][data-panel-group-id="${groupId}"]`));
  1003. }
  1004. function getResizeHandleElementIndex(groupId, id, scope = document) {
  1005. const handles = getResizeHandleElementsForGroup(groupId, scope);
  1006. const index = handles.findIndex(handle => handle.getAttribute(DATA_ATTRIBUTES.resizeHandleId) === id);
  1007. return index !== null && index !== void 0 ? index : null;
  1008. }
  1009. function determinePivotIndices(groupId, dragHandleId, panelGroupElement) {
  1010. const index = getResizeHandleElementIndex(groupId, dragHandleId, panelGroupElement);
  1011. return index != null ? [index, index + 1] : [-1, -1];
  1012. }
  1013. function getPanelGroupElement(id, rootElement = document) {
  1014. var _dataset;
  1015. //If the root element is the PanelGroup
  1016. if (rootElement instanceof HTMLElement && (rootElement === null || rootElement === void 0 ? void 0 : (_dataset = rootElement.dataset) === null || _dataset === void 0 ? void 0 : _dataset.panelGroupId) == id) {
  1017. return rootElement;
  1018. }
  1019. //Else query children
  1020. const element = rootElement.querySelector(`[data-panel-group][data-panel-group-id="${id}"]`);
  1021. if (element) {
  1022. return element;
  1023. }
  1024. return null;
  1025. }
  1026. function getResizeHandleElement(id, scope = document) {
  1027. const element = scope.querySelector(`[${DATA_ATTRIBUTES.resizeHandleId}="${id}"]`);
  1028. if (element) {
  1029. return element;
  1030. }
  1031. return null;
  1032. }
  1033. function getResizeHandlePanelIds(groupId, handleId, panelsArray, scope = document) {
  1034. var _panelsArray$index$id, _panelsArray$index, _panelsArray$id, _panelsArray;
  1035. const handle = getResizeHandleElement(handleId, scope);
  1036. const handles = getResizeHandleElementsForGroup(groupId, scope);
  1037. const index = handle ? handles.indexOf(handle) : -1;
  1038. 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;
  1039. const idAfter = (_panelsArray$id = (_panelsArray = panelsArray[index + 1]) === null || _panelsArray === void 0 ? void 0 : _panelsArray.id) !== null && _panelsArray$id !== void 0 ? _panelsArray$id : null;
  1040. return [idBefore, idAfter];
  1041. }
  1042. // https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
  1043. function useWindowSplitterPanelGroupBehavior({
  1044. committedValuesRef,
  1045. eagerValuesRef,
  1046. groupId,
  1047. layout,
  1048. panelDataArray,
  1049. panelGroupElement,
  1050. setLayout
  1051. }) {
  1052. const devWarningsRef = useRef({
  1053. didWarnAboutMissingResizeHandle: false
  1054. });
  1055. useIsomorphicLayoutEffect(() => {
  1056. if (!panelGroupElement) {
  1057. return;
  1058. }
  1059. const resizeHandleElements = getResizeHandleElementsForGroup(groupId, panelGroupElement);
  1060. for (let index = 0; index < panelDataArray.length - 1; index++) {
  1061. const {
  1062. valueMax,
  1063. valueMin,
  1064. valueNow
  1065. } = calculateAriaValues({
  1066. layout,
  1067. panelsArray: panelDataArray,
  1068. pivotIndices: [index, index + 1]
  1069. });
  1070. const resizeHandleElement = resizeHandleElements[index];
  1071. if (resizeHandleElement == null) {
  1072. {
  1073. const {
  1074. didWarnAboutMissingResizeHandle
  1075. } = devWarningsRef.current;
  1076. if (!didWarnAboutMissingResizeHandle) {
  1077. devWarningsRef.current.didWarnAboutMissingResizeHandle = true;
  1078. console.warn(`WARNING: Missing resize handle for PanelGroup "${groupId}"`);
  1079. }
  1080. }
  1081. } else {
  1082. const panelData = panelDataArray[index];
  1083. assert(panelData, `No panel data found for index "${index}"`);
  1084. resizeHandleElement.setAttribute("aria-controls", panelData.id);
  1085. resizeHandleElement.setAttribute("aria-valuemax", "" + Math.round(valueMax));
  1086. resizeHandleElement.setAttribute("aria-valuemin", "" + Math.round(valueMin));
  1087. resizeHandleElement.setAttribute("aria-valuenow", valueNow != null ? "" + Math.round(valueNow) : "");
  1088. }
  1089. }
  1090. return () => {
  1091. resizeHandleElements.forEach((resizeHandleElement, index) => {
  1092. resizeHandleElement.removeAttribute("aria-controls");
  1093. resizeHandleElement.removeAttribute("aria-valuemax");
  1094. resizeHandleElement.removeAttribute("aria-valuemin");
  1095. resizeHandleElement.removeAttribute("aria-valuenow");
  1096. });
  1097. };
  1098. }, [groupId, layout, panelDataArray, panelGroupElement]);
  1099. useEffect(() => {
  1100. if (!panelGroupElement) {
  1101. return;
  1102. }
  1103. const eagerValues = eagerValuesRef.current;
  1104. assert(eagerValues, `Eager values not found`);
  1105. const {
  1106. panelDataArray
  1107. } = eagerValues;
  1108. const groupElement = getPanelGroupElement(groupId, panelGroupElement);
  1109. assert(groupElement != null, `No group found for id "${groupId}"`);
  1110. const handles = getResizeHandleElementsForGroup(groupId, panelGroupElement);
  1111. assert(handles, `No resize handles found for group id "${groupId}"`);
  1112. const cleanupFunctions = handles.map(handle => {
  1113. const handleId = handle.getAttribute(DATA_ATTRIBUTES.resizeHandleId);
  1114. assert(handleId, `Resize handle element has no handle id attribute`);
  1115. const [idBefore, idAfter] = getResizeHandlePanelIds(groupId, handleId, panelDataArray, panelGroupElement);
  1116. if (idBefore == null || idAfter == null) {
  1117. return () => {};
  1118. }
  1119. const onKeyDown = event => {
  1120. if (event.defaultPrevented) {
  1121. return;
  1122. }
  1123. switch (event.key) {
  1124. case "Enter":
  1125. {
  1126. event.preventDefault();
  1127. const index = panelDataArray.findIndex(panelData => panelData.id === idBefore);
  1128. if (index >= 0) {
  1129. const panelData = panelDataArray[index];
  1130. assert(panelData, `No panel data found for index ${index}`);
  1131. const size = layout[index];
  1132. const {
  1133. collapsedSize = 0,
  1134. collapsible,
  1135. minSize = 0
  1136. } = panelData.constraints;
  1137. if (size != null && collapsible) {
  1138. const nextLayout = adjustLayoutByDelta({
  1139. delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
  1140. initialLayout: layout,
  1141. panelConstraints: panelDataArray.map(panelData => panelData.constraints),
  1142. pivotIndices: determinePivotIndices(groupId, handleId, panelGroupElement),
  1143. prevLayout: layout,
  1144. trigger: "keyboard"
  1145. });
  1146. if (layout !== nextLayout) {
  1147. setLayout(nextLayout);
  1148. }
  1149. }
  1150. }
  1151. break;
  1152. }
  1153. }
  1154. };
  1155. handle.addEventListener("keydown", onKeyDown);
  1156. return () => {
  1157. handle.removeEventListener("keydown", onKeyDown);
  1158. };
  1159. });
  1160. return () => {
  1161. cleanupFunctions.forEach(cleanupFunction => cleanupFunction());
  1162. };
  1163. }, [panelGroupElement, committedValuesRef, eagerValuesRef, groupId, layout, panelDataArray, setLayout]);
  1164. }
  1165. function areEqual(arrayA, arrayB) {
  1166. if (arrayA.length !== arrayB.length) {
  1167. return false;
  1168. }
  1169. for (let index = 0; index < arrayA.length; index++) {
  1170. if (arrayA[index] !== arrayB[index]) {
  1171. return false;
  1172. }
  1173. }
  1174. return true;
  1175. }
  1176. function getResizeEventCursorPosition(direction, event) {
  1177. const isHorizontal = direction === "horizontal";
  1178. const {
  1179. x,
  1180. y
  1181. } = getResizeEventCoordinates(event);
  1182. return isHorizontal ? x : y;
  1183. }
  1184. function calculateDragOffsetPercentage(event, dragHandleId, direction, initialDragState, panelGroupElement) {
  1185. const isHorizontal = direction === "horizontal";
  1186. const handleElement = getResizeHandleElement(dragHandleId, panelGroupElement);
  1187. assert(handleElement, `No resize handle element found for id "${dragHandleId}"`);
  1188. const groupId = handleElement.getAttribute(DATA_ATTRIBUTES.groupId);
  1189. assert(groupId, `Resize handle element has no group id attribute`);
  1190. let {
  1191. initialCursorPosition
  1192. } = initialDragState;
  1193. const cursorPosition = getResizeEventCursorPosition(direction, event);
  1194. const groupElement = getPanelGroupElement(groupId, panelGroupElement);
  1195. assert(groupElement, `No group element found for id "${groupId}"`);
  1196. const groupRect = groupElement.getBoundingClientRect();
  1197. const groupSizeInPixels = isHorizontal ? groupRect.width : groupRect.height;
  1198. const offsetPixels = cursorPosition - initialCursorPosition;
  1199. const offsetPercentage = offsetPixels / groupSizeInPixels * 100;
  1200. return offsetPercentage;
  1201. }
  1202. // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX
  1203. function calculateDeltaPercentage(event, dragHandleId, direction, initialDragState, keyboardResizeBy, panelGroupElement) {
  1204. if (isKeyDown(event)) {
  1205. const isHorizontal = direction === "horizontal";
  1206. let delta = 0;
  1207. if (event.shiftKey) {
  1208. delta = 100;
  1209. } else if (keyboardResizeBy != null) {
  1210. delta = keyboardResizeBy;
  1211. } else {
  1212. delta = 10;
  1213. }
  1214. let movement = 0;
  1215. switch (event.key) {
  1216. case "ArrowDown":
  1217. movement = isHorizontal ? 0 : delta;
  1218. break;
  1219. case "ArrowLeft":
  1220. movement = isHorizontal ? -delta : 0;
  1221. break;
  1222. case "ArrowRight":
  1223. movement = isHorizontal ? delta : 0;
  1224. break;
  1225. case "ArrowUp":
  1226. movement = isHorizontal ? 0 : -delta;
  1227. break;
  1228. case "End":
  1229. movement = 100;
  1230. break;
  1231. case "Home":
  1232. movement = -100;
  1233. break;
  1234. }
  1235. return movement;
  1236. } else {
  1237. if (initialDragState == null) {
  1238. return 0;
  1239. }
  1240. return calculateDragOffsetPercentage(event, dragHandleId, direction, initialDragState, panelGroupElement);
  1241. }
  1242. }
  1243. function calculateUnsafeDefaultLayout({
  1244. panelDataArray
  1245. }) {
  1246. const layout = Array(panelDataArray.length);
  1247. const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
  1248. let numPanelsWithSizes = 0;
  1249. let remainingSize = 100;
  1250. // Distribute default sizes first
  1251. for (let index = 0; index < panelDataArray.length; index++) {
  1252. const panelConstraints = panelConstraintsArray[index];
  1253. assert(panelConstraints, `Panel constraints not found for index ${index}`);
  1254. const {
  1255. defaultSize
  1256. } = panelConstraints;
  1257. if (defaultSize != null) {
  1258. numPanelsWithSizes++;
  1259. layout[index] = defaultSize;
  1260. remainingSize -= defaultSize;
  1261. }
  1262. }
  1263. // Remaining size should be distributed evenly between panels without default sizes
  1264. for (let index = 0; index < panelDataArray.length; index++) {
  1265. const panelConstraints = panelConstraintsArray[index];
  1266. assert(panelConstraints, `Panel constraints not found for index ${index}`);
  1267. const {
  1268. defaultSize
  1269. } = panelConstraints;
  1270. if (defaultSize != null) {
  1271. continue;
  1272. }
  1273. const numRemainingPanels = panelDataArray.length - numPanelsWithSizes;
  1274. const size = remainingSize / numRemainingPanels;
  1275. numPanelsWithSizes++;
  1276. layout[index] = size;
  1277. remainingSize -= size;
  1278. }
  1279. return layout;
  1280. }
  1281. // Layout should be pre-converted into percentages
  1282. function callPanelCallbacks(panelsArray, layout, panelIdToLastNotifiedSizeMap) {
  1283. layout.forEach((size, index) => {
  1284. const panelData = panelsArray[index];
  1285. assert(panelData, `Panel data not found for index ${index}`);
  1286. const {
  1287. callbacks,
  1288. constraints,
  1289. id: panelId
  1290. } = panelData;
  1291. const {
  1292. collapsedSize = 0,
  1293. collapsible
  1294. } = constraints;
  1295. const lastNotifiedSize = panelIdToLastNotifiedSizeMap[panelId];
  1296. if (lastNotifiedSize == null || size !== lastNotifiedSize) {
  1297. panelIdToLastNotifiedSizeMap[panelId] = size;
  1298. const {
  1299. onCollapse,
  1300. onExpand,
  1301. onResize
  1302. } = callbacks;
  1303. if (onResize) {
  1304. onResize(size, lastNotifiedSize);
  1305. }
  1306. if (collapsible && (onCollapse || onExpand)) {
  1307. if (onExpand && (lastNotifiedSize == null || fuzzyNumbersEqual$1(lastNotifiedSize, collapsedSize)) && !fuzzyNumbersEqual$1(size, collapsedSize)) {
  1308. onExpand();
  1309. }
  1310. if (onCollapse && (lastNotifiedSize == null || !fuzzyNumbersEqual$1(lastNotifiedSize, collapsedSize)) && fuzzyNumbersEqual$1(size, collapsedSize)) {
  1311. onCollapse();
  1312. }
  1313. }
  1314. }
  1315. });
  1316. }
  1317. function compareLayouts(a, b) {
  1318. if (a.length !== b.length) {
  1319. return false;
  1320. } else {
  1321. for (let index = 0; index < a.length; index++) {
  1322. if (a[index] != b[index]) {
  1323. return false;
  1324. }
  1325. }
  1326. }
  1327. return true;
  1328. }
  1329. // This method returns a number between 1 and 100 representing
  1330. // the % of the group's overall space this panel should occupy.
  1331. function computePanelFlexBoxStyle({
  1332. defaultSize,
  1333. dragState,
  1334. layout,
  1335. panelData,
  1336. panelIndex,
  1337. precision = 3
  1338. }) {
  1339. const size = layout[panelIndex];
  1340. let flexGrow;
  1341. if (size == null) {
  1342. // Initial render (before panels have registered themselves)
  1343. // In order to support server rendering, fall back to default size if provided
  1344. flexGrow = defaultSize != undefined ? defaultSize.toPrecision(precision) : "1";
  1345. } else if (panelData.length === 1) {
  1346. // Special case: Single panel group should always fill full width/height
  1347. flexGrow = "1";
  1348. } else {
  1349. flexGrow = size.toPrecision(precision);
  1350. }
  1351. return {
  1352. flexBasis: 0,
  1353. flexGrow,
  1354. flexShrink: 1,
  1355. // Without this, Panel sizes may be unintentionally overridden by their content
  1356. overflow: "hidden",
  1357. // Disable pointer events inside of a panel during resize
  1358. // This avoid edge cases like nested iframes
  1359. pointerEvents: dragState !== null ? "none" : undefined
  1360. };
  1361. }
  1362. function debounce(callback, durationMs = 10) {
  1363. let timeoutId = null;
  1364. let callable = (...args) => {
  1365. if (timeoutId !== null) {
  1366. clearTimeout(timeoutId);
  1367. }
  1368. timeoutId = setTimeout(() => {
  1369. callback(...args);
  1370. }, durationMs);
  1371. };
  1372. return callable;
  1373. }
  1374. // PanelGroup might be rendering in a server-side environment where localStorage is not available
  1375. // or on a browser with cookies/storage disabled.
  1376. // In either case, this function avoids accessing localStorage until needed,
  1377. // and avoids throwing user-visible errors.
  1378. function initializeDefaultStorage(storageObject) {
  1379. try {
  1380. if (typeof localStorage !== "undefined") {
  1381. // Bypass this check for future calls
  1382. storageObject.getItem = name => {
  1383. return localStorage.getItem(name);
  1384. };
  1385. storageObject.setItem = (name, value) => {
  1386. localStorage.setItem(name, value);
  1387. };
  1388. } else {
  1389. throw new Error("localStorage not supported in this environment");
  1390. }
  1391. } catch (error) {
  1392. console.error(error);
  1393. storageObject.getItem = () => null;
  1394. storageObject.setItem = () => {};
  1395. }
  1396. }
  1397. function getPanelGroupKey(autoSaveId) {
  1398. return `react-resizable-panels:${autoSaveId}`;
  1399. }
  1400. // Note that Panel ids might be user-provided (stable) or useId generated (non-deterministic)
  1401. // so they should not be used as part of the serialization key.
  1402. // Using the min/max size attributes should work well enough as a backup.
  1403. // Pre-sorting by minSize allows remembering layouts even if panels are re-ordered/dragged.
  1404. function getPanelKey(panels) {
  1405. return panels.map(panel => {
  1406. const {
  1407. constraints,
  1408. id,
  1409. idIsFromProps,
  1410. order
  1411. } = panel;
  1412. if (idIsFromProps) {
  1413. return id;
  1414. } else {
  1415. return order ? `${order}:${JSON.stringify(constraints)}` : JSON.stringify(constraints);
  1416. }
  1417. }).sort((a, b) => a.localeCompare(b)).join(",");
  1418. }
  1419. function loadSerializedPanelGroupState(autoSaveId, storage) {
  1420. try {
  1421. const panelGroupKey = getPanelGroupKey(autoSaveId);
  1422. const serialized = storage.getItem(panelGroupKey);
  1423. if (serialized) {
  1424. const parsed = JSON.parse(serialized);
  1425. if (typeof parsed === "object" && parsed != null) {
  1426. return parsed;
  1427. }
  1428. }
  1429. } catch (error) {}
  1430. return null;
  1431. }
  1432. function loadPanelGroupState(autoSaveId, panels, storage) {
  1433. var _loadSerializedPanelG, _state$panelKey;
  1434. const state = (_loadSerializedPanelG = loadSerializedPanelGroupState(autoSaveId, storage)) !== null && _loadSerializedPanelG !== void 0 ? _loadSerializedPanelG : {};
  1435. const panelKey = getPanelKey(panels);
  1436. return (_state$panelKey = state[panelKey]) !== null && _state$panelKey !== void 0 ? _state$panelKey : null;
  1437. }
  1438. function savePanelGroupState(autoSaveId, panels, panelSizesBeforeCollapse, sizes, storage) {
  1439. var _loadSerializedPanelG2;
  1440. const panelGroupKey = getPanelGroupKey(autoSaveId);
  1441. const panelKey = getPanelKey(panels);
  1442. const state = (_loadSerializedPanelG2 = loadSerializedPanelGroupState(autoSaveId, storage)) !== null && _loadSerializedPanelG2 !== void 0 ? _loadSerializedPanelG2 : {};
  1443. state[panelKey] = {
  1444. expandToSizes: Object.fromEntries(panelSizesBeforeCollapse.entries()),
  1445. layout: sizes
  1446. };
  1447. try {
  1448. storage.setItem(panelGroupKey, JSON.stringify(state));
  1449. } catch (error) {
  1450. console.error(error);
  1451. }
  1452. }
  1453. function validatePanelConstraints({
  1454. panelConstraints: panelConstraintsArray,
  1455. panelId,
  1456. panelIndex
  1457. }) {
  1458. {
  1459. const warnings = [];
  1460. const panelConstraints = panelConstraintsArray[panelIndex];
  1461. assert(panelConstraints, `No panel constraints found for index ${panelIndex}`);
  1462. const {
  1463. collapsedSize = 0,
  1464. collapsible = false,
  1465. defaultSize,
  1466. maxSize = 100,
  1467. minSize = 0
  1468. } = panelConstraints;
  1469. if (minSize > maxSize) {
  1470. warnings.push(`min size (${minSize}%) should not be greater than max size (${maxSize}%)`);
  1471. }
  1472. if (defaultSize != null) {
  1473. if (defaultSize < 0) {
  1474. warnings.push("default size should not be less than 0");
  1475. } else if (defaultSize < minSize && (!collapsible || defaultSize !== collapsedSize)) {
  1476. warnings.push("default size should not be less than min size");
  1477. }
  1478. if (defaultSize > 100) {
  1479. warnings.push("default size should not be greater than 100");
  1480. } else if (defaultSize > maxSize) {
  1481. warnings.push("default size should not be greater than max size");
  1482. }
  1483. }
  1484. if (collapsedSize > minSize) {
  1485. warnings.push("collapsed size should not be greater than min size");
  1486. }
  1487. if (warnings.length > 0) {
  1488. const name = panelId != null ? `Panel "${panelId}"` : "Panel";
  1489. console.warn(`${name} has an invalid configuration:\n\n${warnings.join("\n")}`);
  1490. return false;
  1491. }
  1492. }
  1493. return true;
  1494. }
  1495. // All units must be in percentages; pixel values should be pre-converted
  1496. function validatePanelGroupLayout({
  1497. layout: prevLayout,
  1498. panelConstraints
  1499. }) {
  1500. const nextLayout = [...prevLayout];
  1501. const nextLayoutTotalSize = nextLayout.reduce((accumulated, current) => accumulated + current, 0);
  1502. // Validate layout expectations
  1503. if (nextLayout.length !== panelConstraints.length) {
  1504. throw Error(`Invalid ${panelConstraints.length} panel layout: ${nextLayout.map(size => `${size}%`).join(", ")}`);
  1505. } else if (!fuzzyNumbersEqual(nextLayoutTotalSize, 100) && nextLayout.length > 0) {
  1506. // This is not ideal so we should warn about it, but it may be recoverable in some cases
  1507. // (especially if the amount is small)
  1508. {
  1509. console.warn(`WARNING: Invalid layout total size: ${nextLayout.map(size => `${size}%`).join(", ")}. Layout normalization will be applied.`);
  1510. }
  1511. for (let index = 0; index < panelConstraints.length; index++) {
  1512. const unsafeSize = nextLayout[index];
  1513. assert(unsafeSize != null, `No layout data found for index ${index}`);
  1514. const safeSize = 100 / nextLayoutTotalSize * unsafeSize;
  1515. nextLayout[index] = safeSize;
  1516. }
  1517. }
  1518. let remainingSize = 0;
  1519. // First pass: Validate the proposed layout given each panel's constraints
  1520. for (let index = 0; index < panelConstraints.length; index++) {
  1521. const unsafeSize = nextLayout[index];
  1522. assert(unsafeSize != null, `No layout data found for index ${index}`);
  1523. const safeSize = resizePanel({
  1524. panelConstraints,
  1525. panelIndex: index,
  1526. size: unsafeSize
  1527. });
  1528. if (unsafeSize != safeSize) {
  1529. remainingSize += unsafeSize - safeSize;
  1530. nextLayout[index] = safeSize;
  1531. }
  1532. }
  1533. // If there is additional, left over space, assign it to any panel(s) that permits it
  1534. // (It's not worth taking multiple additional passes to evenly distribute)
  1535. if (!fuzzyNumbersEqual(remainingSize, 0)) {
  1536. for (let index = 0; index < panelConstraints.length; index++) {
  1537. const prevSize = nextLayout[index];
  1538. assert(prevSize != null, `No layout data found for index ${index}`);
  1539. const unsafeSize = prevSize + remainingSize;
  1540. const safeSize = resizePanel({
  1541. panelConstraints,
  1542. panelIndex: index,
  1543. size: unsafeSize
  1544. });
  1545. if (prevSize !== safeSize) {
  1546. remainingSize -= safeSize - prevSize;
  1547. nextLayout[index] = safeSize;
  1548. // Once we've used up the remainder, bail
  1549. if (fuzzyNumbersEqual(remainingSize, 0)) {
  1550. break;
  1551. }
  1552. }
  1553. }
  1554. }
  1555. return nextLayout;
  1556. }
  1557. const LOCAL_STORAGE_DEBOUNCE_INTERVAL = 100;
  1558. const defaultStorage = {
  1559. getItem: name => {
  1560. initializeDefaultStorage(defaultStorage);
  1561. return defaultStorage.getItem(name);
  1562. },
  1563. setItem: (name, value) => {
  1564. initializeDefaultStorage(defaultStorage);
  1565. defaultStorage.setItem(name, value);
  1566. }
  1567. };
  1568. const debounceMap = {};
  1569. function PanelGroupWithForwardedRef({
  1570. autoSaveId = null,
  1571. children,
  1572. className: classNameFromProps = "",
  1573. direction,
  1574. forwardedRef,
  1575. id: idFromProps = null,
  1576. onLayout = null,
  1577. keyboardResizeBy = null,
  1578. storage = defaultStorage,
  1579. style: styleFromProps,
  1580. tagName: Type = "div",
  1581. ...rest
  1582. }) {
  1583. const groupId = useUniqueId(idFromProps);
  1584. const panelGroupElementRef = useRef(null);
  1585. const [dragState, setDragState] = useState(null);
  1586. const [layout, setLayout] = useState([]);
  1587. const forceUpdate = useForceUpdate();
  1588. const panelIdToLastNotifiedSizeMapRef = useRef({});
  1589. const panelSizeBeforeCollapseRef = useRef(new Map());
  1590. const prevDeltaRef = useRef(0);
  1591. const committedValuesRef = useRef({
  1592. autoSaveId,
  1593. direction,
  1594. dragState,
  1595. id: groupId,
  1596. keyboardResizeBy,
  1597. onLayout,
  1598. storage
  1599. });
  1600. const eagerValuesRef = useRef({
  1601. layout,
  1602. panelDataArray: [],
  1603. panelDataArrayChanged: false
  1604. });
  1605. const devWarningsRef = useRef({
  1606. didLogIdAndOrderWarning: false,
  1607. didLogPanelConstraintsWarning: false,
  1608. prevPanelIds: []
  1609. });
  1610. useImperativeHandle(forwardedRef, () => ({
  1611. getId: () => committedValuesRef.current.id,
  1612. getLayout: () => {
  1613. const {
  1614. layout
  1615. } = eagerValuesRef.current;
  1616. return layout;
  1617. },
  1618. setLayout: unsafeLayout => {
  1619. const {
  1620. onLayout
  1621. } = committedValuesRef.current;
  1622. const {
  1623. layout: prevLayout,
  1624. panelDataArray
  1625. } = eagerValuesRef.current;
  1626. const safeLayout = validatePanelGroupLayout({
  1627. layout: unsafeLayout,
  1628. panelConstraints: panelDataArray.map(panelData => panelData.constraints)
  1629. });
  1630. if (!areEqual(prevLayout, safeLayout)) {
  1631. setLayout(safeLayout);
  1632. eagerValuesRef.current.layout = safeLayout;
  1633. if (onLayout) {
  1634. onLayout(safeLayout);
  1635. }
  1636. callPanelCallbacks(panelDataArray, safeLayout, panelIdToLastNotifiedSizeMapRef.current);
  1637. }
  1638. }
  1639. }), []);
  1640. useIsomorphicLayoutEffect(() => {
  1641. committedValuesRef.current.autoSaveId = autoSaveId;
  1642. committedValuesRef.current.direction = direction;
  1643. committedValuesRef.current.dragState = dragState;
  1644. committedValuesRef.current.id = groupId;
  1645. committedValuesRef.current.onLayout = onLayout;
  1646. committedValuesRef.current.storage = storage;
  1647. });
  1648. useWindowSplitterPanelGroupBehavior({
  1649. committedValuesRef,
  1650. eagerValuesRef,
  1651. groupId,
  1652. layout,
  1653. panelDataArray: eagerValuesRef.current.panelDataArray,
  1654. setLayout,
  1655. panelGroupElement: panelGroupElementRef.current
  1656. });
  1657. useEffect(() => {
  1658. const {
  1659. panelDataArray
  1660. } = eagerValuesRef.current;
  1661. // If this panel has been configured to persist sizing information, save sizes to local storage.
  1662. if (autoSaveId) {
  1663. if (layout.length === 0 || layout.length !== panelDataArray.length) {
  1664. return;
  1665. }
  1666. let debouncedSave = debounceMap[autoSaveId];
  1667. // Limit the frequency of localStorage updates.
  1668. if (debouncedSave == null) {
  1669. debouncedSave = debounce(savePanelGroupState, LOCAL_STORAGE_DEBOUNCE_INTERVAL);
  1670. debounceMap[autoSaveId] = debouncedSave;
  1671. }
  1672. // Clone mutable data before passing to the debounced function,
  1673. // else we run the risk of saving an incorrect combination of mutable and immutable values to state.
  1674. const clonedPanelDataArray = [...panelDataArray];
  1675. const clonedPanelSizesBeforeCollapse = new Map(panelSizeBeforeCollapseRef.current);
  1676. debouncedSave(autoSaveId, clonedPanelDataArray, clonedPanelSizesBeforeCollapse, layout, storage);
  1677. }
  1678. }, [autoSaveId, layout, storage]);
  1679. // DEV warnings
  1680. useEffect(() => {
  1681. {
  1682. const {
  1683. panelDataArray
  1684. } = eagerValuesRef.current;
  1685. const {
  1686. didLogIdAndOrderWarning,
  1687. didLogPanelConstraintsWarning,
  1688. prevPanelIds
  1689. } = devWarningsRef.current;
  1690. if (!didLogIdAndOrderWarning) {
  1691. const panelIds = panelDataArray.map(({
  1692. id
  1693. }) => id);
  1694. devWarningsRef.current.prevPanelIds = panelIds;
  1695. const panelsHaveChanged = prevPanelIds.length > 0 && !areEqual(prevPanelIds, panelIds);
  1696. if (panelsHaveChanged) {
  1697. if (panelDataArray.find(({
  1698. idIsFromProps,
  1699. order
  1700. }) => !idIsFromProps || order == null)) {
  1701. devWarningsRef.current.didLogIdAndOrderWarning = true;
  1702. console.warn(`WARNING: Panel id and order props recommended when panels are dynamically rendered`);
  1703. }
  1704. }
  1705. }
  1706. if (!didLogPanelConstraintsWarning) {
  1707. const panelConstraints = panelDataArray.map(panelData => panelData.constraints);
  1708. for (let panelIndex = 0; panelIndex < panelConstraints.length; panelIndex++) {
  1709. const panelData = panelDataArray[panelIndex];
  1710. assert(panelData, `Panel data not found for index ${panelIndex}`);
  1711. const isValid = validatePanelConstraints({
  1712. panelConstraints,
  1713. panelId: panelData.id,
  1714. panelIndex
  1715. });
  1716. if (!isValid) {
  1717. devWarningsRef.current.didLogPanelConstraintsWarning = true;
  1718. break;
  1719. }
  1720. }
  1721. }
  1722. }
  1723. });
  1724. // External APIs are safe to memoize via committed values ref
  1725. const collapsePanel = useCallback(panelData => {
  1726. const {
  1727. onLayout
  1728. } = committedValuesRef.current;
  1729. const {
  1730. layout: prevLayout,
  1731. panelDataArray
  1732. } = eagerValuesRef.current;
  1733. if (panelData.constraints.collapsible) {
  1734. const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
  1735. const {
  1736. collapsedSize = 0,
  1737. panelSize,
  1738. pivotIndices
  1739. } = panelDataHelper(panelDataArray, panelData, prevLayout);
  1740. assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
  1741. if (!fuzzyNumbersEqual$1(panelSize, collapsedSize)) {
  1742. // Store size before collapse;
  1743. // This is the size that gets restored if the expand() API is used.
  1744. panelSizeBeforeCollapseRef.current.set(panelData.id, panelSize);
  1745. const isLastPanel = findPanelDataIndex(panelDataArray, panelData) === panelDataArray.length - 1;
  1746. const delta = isLastPanel ? panelSize - collapsedSize : collapsedSize - panelSize;
  1747. const nextLayout = adjustLayoutByDelta({
  1748. delta,
  1749. initialLayout: prevLayout,
  1750. panelConstraints: panelConstraintsArray,
  1751. pivotIndices,
  1752. prevLayout,
  1753. trigger: "imperative-api"
  1754. });
  1755. if (!compareLayouts(prevLayout, nextLayout)) {
  1756. setLayout(nextLayout);
  1757. eagerValuesRef.current.layout = nextLayout;
  1758. if (onLayout) {
  1759. onLayout(nextLayout);
  1760. }
  1761. callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
  1762. }
  1763. }
  1764. }
  1765. }, []);
  1766. // External APIs are safe to memoize via committed values ref
  1767. const expandPanel = useCallback((panelData, minSizeOverride) => {
  1768. const {
  1769. onLayout
  1770. } = committedValuesRef.current;
  1771. const {
  1772. layout: prevLayout,
  1773. panelDataArray
  1774. } = eagerValuesRef.current;
  1775. if (panelData.constraints.collapsible) {
  1776. const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
  1777. const {
  1778. collapsedSize = 0,
  1779. panelSize = 0,
  1780. minSize: minSizeFromProps = 0,
  1781. pivotIndices
  1782. } = panelDataHelper(panelDataArray, panelData, prevLayout);
  1783. const minSize = minSizeOverride !== null && minSizeOverride !== void 0 ? minSizeOverride : minSizeFromProps;
  1784. if (fuzzyNumbersEqual$1(panelSize, collapsedSize)) {
  1785. // Restore this panel to the size it was before it was collapsed, if possible.
  1786. const prevPanelSize = panelSizeBeforeCollapseRef.current.get(panelData.id);
  1787. const baseSize = prevPanelSize != null && prevPanelSize >= minSize ? prevPanelSize : minSize;
  1788. const isLastPanel = findPanelDataIndex(panelDataArray, panelData) === panelDataArray.length - 1;
  1789. const delta = isLastPanel ? panelSize - baseSize : baseSize - panelSize;
  1790. const nextLayout = adjustLayoutByDelta({
  1791. delta,
  1792. initialLayout: prevLayout,
  1793. panelConstraints: panelConstraintsArray,
  1794. pivotIndices,
  1795. prevLayout,
  1796. trigger: "imperative-api"
  1797. });
  1798. if (!compareLayouts(prevLayout, nextLayout)) {
  1799. setLayout(nextLayout);
  1800. eagerValuesRef.current.layout = nextLayout;
  1801. if (onLayout) {
  1802. onLayout(nextLayout);
  1803. }
  1804. callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
  1805. }
  1806. }
  1807. }
  1808. }, []);
  1809. // External APIs are safe to memoize via committed values ref
  1810. const getPanelSize = useCallback(panelData => {
  1811. const {
  1812. layout,
  1813. panelDataArray
  1814. } = eagerValuesRef.current;
  1815. const {
  1816. panelSize
  1817. } = panelDataHelper(panelDataArray, panelData, layout);
  1818. assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
  1819. return panelSize;
  1820. }, []);
  1821. // This API should never read from committedValuesRef
  1822. const getPanelStyle = useCallback((panelData, defaultSize) => {
  1823. const {
  1824. panelDataArray
  1825. } = eagerValuesRef.current;
  1826. const panelIndex = findPanelDataIndex(panelDataArray, panelData);
  1827. return computePanelFlexBoxStyle({
  1828. defaultSize,
  1829. dragState,
  1830. layout,
  1831. panelData: panelDataArray,
  1832. panelIndex
  1833. });
  1834. }, [dragState, layout]);
  1835. // External APIs are safe to memoize via committed values ref
  1836. const isPanelCollapsed = useCallback(panelData => {
  1837. const {
  1838. layout,
  1839. panelDataArray
  1840. } = eagerValuesRef.current;
  1841. const {
  1842. collapsedSize = 0,
  1843. collapsible,
  1844. panelSize
  1845. } = panelDataHelper(panelDataArray, panelData, layout);
  1846. assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
  1847. return collapsible === true && fuzzyNumbersEqual$1(panelSize, collapsedSize);
  1848. }, []);
  1849. // External APIs are safe to memoize via committed values ref
  1850. const isPanelExpanded = useCallback(panelData => {
  1851. const {
  1852. layout,
  1853. panelDataArray
  1854. } = eagerValuesRef.current;
  1855. const {
  1856. collapsedSize = 0,
  1857. collapsible,
  1858. panelSize
  1859. } = panelDataHelper(panelDataArray, panelData, layout);
  1860. assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
  1861. return !collapsible || fuzzyCompareNumbers(panelSize, collapsedSize) > 0;
  1862. }, []);
  1863. const registerPanel = useCallback(panelData => {
  1864. const {
  1865. panelDataArray
  1866. } = eagerValuesRef.current;
  1867. panelDataArray.push(panelData);
  1868. panelDataArray.sort((panelA, panelB) => {
  1869. const orderA = panelA.order;
  1870. const orderB = panelB.order;
  1871. if (orderA == null && orderB == null) {
  1872. return 0;
  1873. } else if (orderA == null) {
  1874. return -1;
  1875. } else if (orderB == null) {
  1876. return 1;
  1877. } else {
  1878. return orderA - orderB;
  1879. }
  1880. });
  1881. eagerValuesRef.current.panelDataArrayChanged = true;
  1882. forceUpdate();
  1883. }, [forceUpdate]);
  1884. // (Re)calculate group layout whenever panels are registered or unregistered.
  1885. // eslint-disable-next-line react-hooks/exhaustive-deps
  1886. useIsomorphicLayoutEffect(() => {
  1887. if (eagerValuesRef.current.panelDataArrayChanged) {
  1888. eagerValuesRef.current.panelDataArrayChanged = false;
  1889. const {
  1890. autoSaveId,
  1891. onLayout,
  1892. storage
  1893. } = committedValuesRef.current;
  1894. const {
  1895. layout: prevLayout,
  1896. panelDataArray
  1897. } = eagerValuesRef.current;
  1898. // If this panel has been configured to persist sizing information,
  1899. // default size should be restored from local storage if possible.
  1900. let unsafeLayout = null;
  1901. if (autoSaveId) {
  1902. const state = loadPanelGroupState(autoSaveId, panelDataArray, storage);
  1903. if (state) {
  1904. panelSizeBeforeCollapseRef.current = new Map(Object.entries(state.expandToSizes));
  1905. unsafeLayout = state.layout;
  1906. }
  1907. }
  1908. if (unsafeLayout == null) {
  1909. unsafeLayout = calculateUnsafeDefaultLayout({
  1910. panelDataArray
  1911. });
  1912. }
  1913. // Validate even saved layouts in case something has changed since last render
  1914. // e.g. for pixel groups, this could be the size of the window
  1915. const nextLayout = validatePanelGroupLayout({
  1916. layout: unsafeLayout,
  1917. panelConstraints: panelDataArray.map(panelData => panelData.constraints)
  1918. });
  1919. if (!areEqual(prevLayout, nextLayout)) {
  1920. setLayout(nextLayout);
  1921. eagerValuesRef.current.layout = nextLayout;
  1922. if (onLayout) {
  1923. onLayout(nextLayout);
  1924. }
  1925. callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
  1926. }
  1927. }
  1928. });
  1929. // Reset the cached layout if hidden by the Activity/Offscreen API
  1930. useIsomorphicLayoutEffect(() => {
  1931. const eagerValues = eagerValuesRef.current;
  1932. return () => {
  1933. eagerValues.layout = [];
  1934. };
  1935. }, []);
  1936. const registerResizeHandle = useCallback(dragHandleId => {
  1937. let isRTL = false;
  1938. const panelGroupElement = panelGroupElementRef.current;
  1939. if (panelGroupElement) {
  1940. const style = window.getComputedStyle(panelGroupElement, null);
  1941. if (style.getPropertyValue("direction") === "rtl") {
  1942. isRTL = true;
  1943. }
  1944. }
  1945. return function resizeHandler(event) {
  1946. event.preventDefault();
  1947. const panelGroupElement = panelGroupElementRef.current;
  1948. if (!panelGroupElement) {
  1949. return () => null;
  1950. }
  1951. const {
  1952. direction,
  1953. dragState,
  1954. id: groupId,
  1955. keyboardResizeBy,
  1956. onLayout
  1957. } = committedValuesRef.current;
  1958. const {
  1959. layout: prevLayout,
  1960. panelDataArray
  1961. } = eagerValuesRef.current;
  1962. const {
  1963. initialLayout
  1964. } = dragState !== null && dragState !== void 0 ? dragState : {};
  1965. const pivotIndices = determinePivotIndices(groupId, dragHandleId, panelGroupElement);
  1966. let delta = calculateDeltaPercentage(event, dragHandleId, direction, dragState, keyboardResizeBy, panelGroupElement);
  1967. const isHorizontal = direction === "horizontal";
  1968. if (isHorizontal && isRTL) {
  1969. delta = -delta;
  1970. }
  1971. const panelConstraints = panelDataArray.map(panelData => panelData.constraints);
  1972. const nextLayout = adjustLayoutByDelta({
  1973. delta,
  1974. initialLayout: initialLayout !== null && initialLayout !== void 0 ? initialLayout : prevLayout,
  1975. panelConstraints,
  1976. pivotIndices,
  1977. prevLayout,
  1978. trigger: isKeyDown(event) ? "keyboard" : "mouse-or-touch"
  1979. });
  1980. const layoutChanged = !compareLayouts(prevLayout, nextLayout);
  1981. // Only update the cursor for layout changes triggered by touch/mouse events (not keyboard)
  1982. // Update the cursor even if the layout hasn't changed (we may need to show an invalid cursor state)
  1983. if (isPointerEvent(event) || isMouseEvent(event)) {
  1984. // Watch for multiple subsequent deltas; this might occur for tiny cursor movements.
  1985. // In this case, Panel sizes might not change–
  1986. // but updating cursor in this scenario would cause a flicker.
  1987. if (prevDeltaRef.current != delta) {
  1988. prevDeltaRef.current = delta;
  1989. if (!layoutChanged && delta !== 0) {
  1990. // If the pointer has moved too far to resize the panel any further, note this so we can update the cursor.
  1991. // This mimics VS Code behavior.
  1992. if (isHorizontal) {
  1993. reportConstraintsViolation(dragHandleId, delta < 0 ? EXCEEDED_HORIZONTAL_MIN : EXCEEDED_HORIZONTAL_MAX);
  1994. } else {
  1995. reportConstraintsViolation(dragHandleId, delta < 0 ? EXCEEDED_VERTICAL_MIN : EXCEEDED_VERTICAL_MAX);
  1996. }
  1997. } else {
  1998. reportConstraintsViolation(dragHandleId, 0);
  1999. }
  2000. }
  2001. }
  2002. if (layoutChanged) {
  2003. setLayout(nextLayout);
  2004. eagerValuesRef.current.layout = nextLayout;
  2005. if (onLayout) {
  2006. onLayout(nextLayout);
  2007. }
  2008. callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
  2009. }
  2010. };
  2011. }, []);
  2012. // External APIs are safe to memoize via committed values ref
  2013. const resizePanel = useCallback((panelData, unsafePanelSize) => {
  2014. const {
  2015. onLayout
  2016. } = committedValuesRef.current;
  2017. const {
  2018. layout: prevLayout,
  2019. panelDataArray
  2020. } = eagerValuesRef.current;
  2021. const panelConstraintsArray = panelDataArray.map(panelData => panelData.constraints);
  2022. const {
  2023. panelSize,
  2024. pivotIndices
  2025. } = panelDataHelper(panelDataArray, panelData, prevLayout);
  2026. assert(panelSize != null, `Panel size not found for panel "${panelData.id}"`);
  2027. const isLastPanel = findPanelDataIndex(panelDataArray, panelData) === panelDataArray.length - 1;
  2028. const delta = isLastPanel ? panelSize - unsafePanelSize : unsafePanelSize - panelSize;
  2029. const nextLayout = adjustLayoutByDelta({
  2030. delta,
  2031. initialLayout: prevLayout,
  2032. panelConstraints: panelConstraintsArray,
  2033. pivotIndices,
  2034. prevLayout,
  2035. trigger: "imperative-api"
  2036. });
  2037. if (!compareLayouts(prevLayout, nextLayout)) {
  2038. setLayout(nextLayout);
  2039. eagerValuesRef.current.layout = nextLayout;
  2040. if (onLayout) {
  2041. onLayout(nextLayout);
  2042. }
  2043. callPanelCallbacks(panelDataArray, nextLayout, panelIdToLastNotifiedSizeMapRef.current);
  2044. }
  2045. }, []);
  2046. const reevaluatePanelConstraints = useCallback((panelData, prevConstraints) => {
  2047. const {
  2048. layout,
  2049. panelDataArray
  2050. } = eagerValuesRef.current;
  2051. const {
  2052. collapsedSize: prevCollapsedSize = 0,
  2053. collapsible: prevCollapsible
  2054. } = prevConstraints;
  2055. const {
  2056. collapsedSize: nextCollapsedSize = 0,
  2057. collapsible: nextCollapsible,
  2058. maxSize: nextMaxSize = 100,
  2059. minSize: nextMinSize = 0
  2060. } = panelData.constraints;
  2061. const {
  2062. panelSize: prevPanelSize
  2063. } = panelDataHelper(panelDataArray, panelData, layout);
  2064. if (prevPanelSize == null) {
  2065. // It's possible that the panels in this group have changed since the last render
  2066. return;
  2067. }
  2068. if (prevCollapsible && nextCollapsible && fuzzyNumbersEqual$1(prevPanelSize, prevCollapsedSize)) {
  2069. if (!fuzzyNumbersEqual$1(prevCollapsedSize, nextCollapsedSize)) {
  2070. resizePanel(panelData, nextCollapsedSize);
  2071. }
  2072. } else if (prevPanelSize < nextMinSize) {
  2073. resizePanel(panelData, nextMinSize);
  2074. } else if (prevPanelSize > nextMaxSize) {
  2075. resizePanel(panelData, nextMaxSize);
  2076. }
  2077. }, [resizePanel]);
  2078. // TODO Multiple drag handles can be active at the same time so this API is a bit awkward now
  2079. const startDragging = useCallback((dragHandleId, event) => {
  2080. const {
  2081. direction
  2082. } = committedValuesRef.current;
  2083. const {
  2084. layout
  2085. } = eagerValuesRef.current;
  2086. if (!panelGroupElementRef.current) {
  2087. return;
  2088. }
  2089. const handleElement = getResizeHandleElement(dragHandleId, panelGroupElementRef.current);
  2090. assert(handleElement, `Drag handle element not found for id "${dragHandleId}"`);
  2091. const initialCursorPosition = getResizeEventCursorPosition(direction, event);
  2092. setDragState({
  2093. dragHandleId,
  2094. dragHandleRect: handleElement.getBoundingClientRect(),
  2095. initialCursorPosition,
  2096. initialLayout: layout
  2097. });
  2098. }, []);
  2099. const stopDragging = useCallback(() => {
  2100. setDragState(null);
  2101. }, []);
  2102. const unregisterPanel = useCallback(panelData => {
  2103. const {
  2104. panelDataArray
  2105. } = eagerValuesRef.current;
  2106. const index = findPanelDataIndex(panelDataArray, panelData);
  2107. if (index >= 0) {
  2108. panelDataArray.splice(index, 1);
  2109. // TRICKY
  2110. // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
  2111. // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
  2112. // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
  2113. delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
  2114. eagerValuesRef.current.panelDataArrayChanged = true;
  2115. forceUpdate();
  2116. }
  2117. }, [forceUpdate]);
  2118. const context = useMemo(() => ({
  2119. collapsePanel,
  2120. direction,
  2121. dragState,
  2122. expandPanel,
  2123. getPanelSize,
  2124. getPanelStyle,
  2125. groupId,
  2126. isPanelCollapsed,
  2127. isPanelExpanded,
  2128. reevaluatePanelConstraints,
  2129. registerPanel,
  2130. registerResizeHandle,
  2131. resizePanel,
  2132. startDragging,
  2133. stopDragging,
  2134. unregisterPanel,
  2135. panelGroupElement: panelGroupElementRef.current
  2136. }), [collapsePanel, dragState, direction, expandPanel, getPanelSize, getPanelStyle, groupId, isPanelCollapsed, isPanelExpanded, reevaluatePanelConstraints, registerPanel, registerResizeHandle, resizePanel, startDragging, stopDragging, unregisterPanel]);
  2137. const style = {
  2138. display: "flex",
  2139. flexDirection: direction === "horizontal" ? "row" : "column",
  2140. height: "100%",
  2141. overflow: "hidden",
  2142. width: "100%"
  2143. };
  2144. return createElement(PanelGroupContext.Provider, {
  2145. value: context
  2146. }, createElement(Type, {
  2147. ...rest,
  2148. children,
  2149. className: classNameFromProps,
  2150. id: idFromProps,
  2151. ref: panelGroupElementRef,
  2152. style: {
  2153. ...style,
  2154. ...styleFromProps
  2155. },
  2156. // CSS selectors
  2157. [DATA_ATTRIBUTES.group]: "",
  2158. [DATA_ATTRIBUTES.groupDirection]: direction,
  2159. [DATA_ATTRIBUTES.groupId]: groupId
  2160. }));
  2161. }
  2162. const PanelGroup = forwardRef((props, ref) => createElement(PanelGroupWithForwardedRef, {
  2163. ...props,
  2164. forwardedRef: ref
  2165. }));
  2166. PanelGroupWithForwardedRef.displayName = "PanelGroup";
  2167. PanelGroup.displayName = "forwardRef(PanelGroup)";
  2168. function findPanelDataIndex(panelDataArray, panelData) {
  2169. return panelDataArray.findIndex(prevPanelData => prevPanelData === panelData || prevPanelData.id === panelData.id);
  2170. }
  2171. function panelDataHelper(panelDataArray, panelData, layout) {
  2172. const panelIndex = findPanelDataIndex(panelDataArray, panelData);
  2173. const isLastPanel = panelIndex === panelDataArray.length - 1;
  2174. const pivotIndices = isLastPanel ? [panelIndex - 1, panelIndex] : [panelIndex, panelIndex + 1];
  2175. const panelSize = layout[panelIndex];
  2176. return {
  2177. ...panelData.constraints,
  2178. panelSize,
  2179. pivotIndices
  2180. };
  2181. }
  2182. // https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
  2183. function useWindowSplitterResizeHandlerBehavior({
  2184. disabled,
  2185. handleId,
  2186. resizeHandler,
  2187. panelGroupElement
  2188. }) {
  2189. useEffect(() => {
  2190. if (disabled || resizeHandler == null || panelGroupElement == null) {
  2191. return;
  2192. }
  2193. const handleElement = getResizeHandleElement(handleId, panelGroupElement);
  2194. if (handleElement == null) {
  2195. return;
  2196. }
  2197. const onKeyDown = event => {
  2198. if (event.defaultPrevented) {
  2199. return;
  2200. }
  2201. switch (event.key) {
  2202. case "ArrowDown":
  2203. case "ArrowLeft":
  2204. case "ArrowRight":
  2205. case "ArrowUp":
  2206. case "End":
  2207. case "Home":
  2208. {
  2209. event.preventDefault();
  2210. resizeHandler(event);
  2211. break;
  2212. }
  2213. case "F6":
  2214. {
  2215. event.preventDefault();
  2216. const groupId = handleElement.getAttribute(DATA_ATTRIBUTES.groupId);
  2217. assert(groupId, `No group element found for id "${groupId}"`);
  2218. const handles = getResizeHandleElementsForGroup(groupId, panelGroupElement);
  2219. const index = getResizeHandleElementIndex(groupId, handleId, panelGroupElement);
  2220. assert(index !== null, `No resize element found for id "${handleId}"`);
  2221. const nextIndex = event.shiftKey ? index > 0 ? index - 1 : handles.length - 1 : index + 1 < handles.length ? index + 1 : 0;
  2222. const nextHandle = handles[nextIndex];
  2223. nextHandle.focus();
  2224. break;
  2225. }
  2226. }
  2227. };
  2228. handleElement.addEventListener("keydown", onKeyDown);
  2229. return () => {
  2230. handleElement.removeEventListener("keydown", onKeyDown);
  2231. };
  2232. }, [panelGroupElement, disabled, handleId, resizeHandler]);
  2233. }
  2234. function PanelResizeHandle({
  2235. children = null,
  2236. className: classNameFromProps = "",
  2237. disabled = false,
  2238. hitAreaMargins,
  2239. id: idFromProps,
  2240. onBlur,
  2241. onClick,
  2242. onDragging,
  2243. onFocus,
  2244. onPointerDown,
  2245. onPointerUp,
  2246. style: styleFromProps = {},
  2247. tabIndex = 0,
  2248. tagName: Type = "div",
  2249. ...rest
  2250. }) {
  2251. var _hitAreaMargins$coars, _hitAreaMargins$fine;
  2252. const elementRef = useRef(null);
  2253. // Use a ref to guard against users passing inline props
  2254. const callbacksRef = useRef({
  2255. onClick,
  2256. onDragging,
  2257. onPointerDown,
  2258. onPointerUp
  2259. });
  2260. useEffect(() => {
  2261. callbacksRef.current.onClick = onClick;
  2262. callbacksRef.current.onDragging = onDragging;
  2263. callbacksRef.current.onPointerDown = onPointerDown;
  2264. callbacksRef.current.onPointerUp = onPointerUp;
  2265. });
  2266. const panelGroupContext = useContext(PanelGroupContext);
  2267. if (panelGroupContext === null) {
  2268. throw Error(`PanelResizeHandle components must be rendered within a PanelGroup container`);
  2269. }
  2270. const {
  2271. direction,
  2272. groupId,
  2273. registerResizeHandle: registerResizeHandleWithParentGroup,
  2274. startDragging,
  2275. stopDragging,
  2276. panelGroupElement
  2277. } = panelGroupContext;
  2278. const resizeHandleId = useUniqueId(idFromProps);
  2279. const [state, setState] = useState("inactive");
  2280. const [isFocused, setIsFocused] = useState(false);
  2281. const [resizeHandler, setResizeHandler] = useState(null);
  2282. const committedValuesRef = useRef({
  2283. state
  2284. });
  2285. useIsomorphicLayoutEffect(() => {
  2286. committedValuesRef.current.state = state;
  2287. });
  2288. useEffect(() => {
  2289. if (disabled) {
  2290. setResizeHandler(null);
  2291. } else {
  2292. const resizeHandler = registerResizeHandleWithParentGroup(resizeHandleId);
  2293. setResizeHandler(() => resizeHandler);
  2294. }
  2295. }, [disabled, resizeHandleId, registerResizeHandleWithParentGroup]);
  2296. // Extract hit area margins before passing them to the effect's dependency array
  2297. // so that inline object values won't trigger re-renders
  2298. const coarseHitAreaMargins = (_hitAreaMargins$coars = hitAreaMargins === null || hitAreaMargins === void 0 ? void 0 : hitAreaMargins.coarse) !== null && _hitAreaMargins$coars !== void 0 ? _hitAreaMargins$coars : 15;
  2299. const fineHitAreaMargins = (_hitAreaMargins$fine = hitAreaMargins === null || hitAreaMargins === void 0 ? void 0 : hitAreaMargins.fine) !== null && _hitAreaMargins$fine !== void 0 ? _hitAreaMargins$fine : 5;
  2300. useEffect(() => {
  2301. if (disabled || resizeHandler == null) {
  2302. return;
  2303. }
  2304. const element = elementRef.current;
  2305. assert(element, "Element ref not attached");
  2306. let didMove = false;
  2307. const setResizeHandlerState = (action, isActive, event) => {
  2308. if (!isActive) {
  2309. setState("inactive");
  2310. return;
  2311. }
  2312. switch (action) {
  2313. case "down":
  2314. {
  2315. setState("drag");
  2316. didMove = false;
  2317. assert(event, 'Expected event to be defined for "down" action');
  2318. startDragging(resizeHandleId, event);
  2319. const {
  2320. onDragging,
  2321. onPointerDown
  2322. } = callbacksRef.current;
  2323. onDragging === null || onDragging === void 0 ? void 0 : onDragging(true);
  2324. onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown();
  2325. break;
  2326. }
  2327. case "move":
  2328. {
  2329. const {
  2330. state
  2331. } = committedValuesRef.current;
  2332. didMove = true;
  2333. if (state !== "drag") {
  2334. setState("hover");
  2335. }
  2336. assert(event, 'Expected event to be defined for "move" action');
  2337. resizeHandler(event);
  2338. break;
  2339. }
  2340. case "up":
  2341. {
  2342. setState("hover");
  2343. stopDragging();
  2344. const {
  2345. onClick,
  2346. onDragging,
  2347. onPointerUp
  2348. } = callbacksRef.current;
  2349. onDragging === null || onDragging === void 0 ? void 0 : onDragging(false);
  2350. onPointerUp === null || onPointerUp === void 0 ? void 0 : onPointerUp();
  2351. if (!didMove) {
  2352. onClick === null || onClick === void 0 ? void 0 : onClick();
  2353. }
  2354. break;
  2355. }
  2356. }
  2357. };
  2358. return registerResizeHandle(resizeHandleId, element, direction, {
  2359. coarse: coarseHitAreaMargins,
  2360. fine: fineHitAreaMargins
  2361. }, setResizeHandlerState);
  2362. }, [coarseHitAreaMargins, direction, disabled, fineHitAreaMargins, registerResizeHandleWithParentGroup, resizeHandleId, resizeHandler, startDragging, stopDragging]);
  2363. useWindowSplitterResizeHandlerBehavior({
  2364. disabled,
  2365. handleId: resizeHandleId,
  2366. resizeHandler,
  2367. panelGroupElement
  2368. });
  2369. const style = {
  2370. touchAction: "none",
  2371. userSelect: "none"
  2372. };
  2373. return createElement(Type, {
  2374. ...rest,
  2375. children,
  2376. className: classNameFromProps,
  2377. id: idFromProps,
  2378. onBlur: () => {
  2379. setIsFocused(false);
  2380. onBlur === null || onBlur === void 0 ? void 0 : onBlur();
  2381. },
  2382. onFocus: () => {
  2383. setIsFocused(true);
  2384. onFocus === null || onFocus === void 0 ? void 0 : onFocus();
  2385. },
  2386. ref: elementRef,
  2387. role: "separator",
  2388. style: {
  2389. ...style,
  2390. ...styleFromProps
  2391. },
  2392. tabIndex,
  2393. // CSS selectors
  2394. [DATA_ATTRIBUTES.groupDirection]: direction,
  2395. [DATA_ATTRIBUTES.groupId]: groupId,
  2396. [DATA_ATTRIBUTES.resizeHandle]: "",
  2397. [DATA_ATTRIBUTES.resizeHandleActive]: state === "drag" ? "pointer" : isFocused ? "keyboard" : undefined,
  2398. [DATA_ATTRIBUTES.resizeHandleEnabled]: !disabled,
  2399. [DATA_ATTRIBUTES.resizeHandleId]: resizeHandleId,
  2400. [DATA_ATTRIBUTES.resizeHandleState]: state
  2401. });
  2402. }
  2403. PanelResizeHandle.displayName = "PanelResizeHandle";
  2404. function getPanelElement(id, scope = document) {
  2405. const element = scope.querySelector(`[data-panel-id="${id}"]`);
  2406. if (element) {
  2407. return element;
  2408. }
  2409. return null;
  2410. }
  2411. function getPanelElementsForGroup(groupId, scope = document) {
  2412. return Array.from(scope.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
  2413. }
  2414. function getIntersectingRectangle(rectOne, rectTwo, strict) {
  2415. if (!intersects(rectOne, rectTwo, strict)) {
  2416. return {
  2417. x: 0,
  2418. y: 0,
  2419. width: 0,
  2420. height: 0
  2421. };
  2422. }
  2423. return {
  2424. x: Math.max(rectOne.x, rectTwo.x),
  2425. y: Math.max(rectOne.y, rectTwo.y),
  2426. width: Math.min(rectOne.x + rectOne.width, rectTwo.x + rectTwo.width) - Math.max(rectOne.x, rectTwo.x),
  2427. height: Math.min(rectOne.y + rectOne.height, rectTwo.y + rectTwo.height) - Math.max(rectOne.y, rectTwo.y)
  2428. };
  2429. }
  2430. export { DATA_ATTRIBUTES, Panel, PanelGroup, PanelResizeHandle, assert, disableGlobalCursorStyles, enableGlobalCursorStyles, getIntersectingRectangle, getPanelElement, getPanelElementsForGroup, getPanelGroupElement, getResizeHandleElement, getResizeHandleElementIndex, getResizeHandleElementsForGroup, getResizeHandlePanelIds, intersects, setNonce };