react-resizable-panels.esm.js 81 KB

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