react-resizable-panels.development.node.esm.js 77 KB

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