react-resizable-panels.development.cjs.js 86 KB

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