react-resizable-panels.browser.esm.js 81 KB

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