@window-splitter/state 0.5.8 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,12 +1,5 @@
1
1
  import * as Cookies from "tiny-cookie";
2
2
  import { raf } from "@react-spring/rafz";
3
- import {
4
- createMachine,
5
- assign,
6
- enqueueActions,
7
- fromPromise,
8
- Snapshot,
9
- } from "xstate";
10
3
  import invariant from "tiny-invariant";
11
4
  import Big from "big.js";
12
5
 
@@ -328,10 +321,13 @@ export interface GroupMachineContextValue {
328
321
  autosaveStrategy?: "localStorage" | "cookie";
329
322
  }
330
323
 
331
- export type GroupMachineSnapshot = Snapshot<unknown> & {
332
- context: GroupMachineContextValue;
333
- value: string;
334
- };
324
+ interface LockGroupEvent {
325
+ type: "lockGroup";
326
+ }
327
+
328
+ interface UnlockGroupEvent {
329
+ type: "unlockGroup";
330
+ }
335
331
 
336
332
  export type GroupMachineEvent =
337
333
  | RegisterPanelEvent
@@ -350,7 +346,9 @@ export type GroupMachineEvent =
350
346
  | ApplyDeltaEvent
351
347
  | SetActualItemsSizeEvent
352
348
  | RebindPanelCallbacksEvent
353
- | UpdateConstraintsEvent;
349
+ | UpdateConstraintsEvent
350
+ | LockGroupEvent
351
+ | UnlockGroupEvent;
354
352
 
355
353
  type EventForType<T extends GroupMachineEvent["type"]> = Extract<
356
354
  GroupMachineEvent,
@@ -383,12 +381,14 @@ export function getCursor(
383
381
  }
384
382
  }
385
383
 
386
- export function prepareSnapshot(snapshot: GroupMachineSnapshot) {
387
- const snapshotContext = snapshot.context;
384
+ export function prepareSnapshot(snapshot: GroupMachineContextValue) {
385
+ if (!("items" in snapshot)) {
386
+ return;
387
+ }
388
388
 
389
- snapshotContext.dragOvershoot = new Big(snapshotContext.dragOvershoot);
389
+ snapshot.dragOvershoot = new Big(snapshot.dragOvershoot || 0);
390
390
 
391
- for (const item of snapshotContext.items) {
391
+ for (const item of snapshot.items) {
392
392
  if (isPanelData(item)) {
393
393
  item.currentValue.value = new Big(item.currentValue.value);
394
394
  item.collapsedSize.value = new Big(item.collapsedSize.value);
@@ -1558,12 +1558,6 @@ function clearLastKnownSize(items: Item[]) {
1558
1558
 
1559
1559
  // #region Machine
1560
1560
 
1561
- interface AnimationActorInput {
1562
- context: GroupMachineContextValue;
1563
- event: CollapsePanelEvent | ExpandPanelEvent;
1564
- send: (event: GroupMachineEvent) => void;
1565
- }
1566
-
1567
1561
  interface AnimationActorOutput {
1568
1562
  panelId: string;
1569
1563
  action: "expand" | "collapse";
@@ -1585,593 +1579,601 @@ function getDeltaForEvent(
1585
1579
  return panel.currentValue.value.minus(collapsedSize);
1586
1580
  }
1587
1581
 
1588
- const animationActor = fromPromise<
1589
- AnimationActorOutput | undefined,
1590
- AnimationActorInput
1591
- >(
1592
- ({ input: { send, context, event } }) =>
1593
- new Promise<AnimationActorOutput | undefined>((resolve) => {
1594
- const panel = getPanelWithId(context, event.panelId);
1595
- const handle = getHandleForPanelId(context, event.panelId);
1582
+ function animationActor(
1583
+ context: GroupMachineContextValue,
1584
+ event: CollapsePanelEvent | ExpandPanelEvent,
1585
+ send: (e: GroupMachineEvent) => void,
1586
+ abortController: AbortController
1587
+ ) {
1588
+ const panel = getPanelWithId(context, event.panelId);
1589
+ const handle = getHandleForPanelId(context, event.panelId);
1596
1590
 
1597
- let direction = new Big(handle.direction);
1598
- const fullDelta = getDeltaForEvent(context, event);
1591
+ let direction = new Big(handle.direction);
1592
+ const fullDelta = getDeltaForEvent(context, event);
1599
1593
 
1600
- if (event.type === "collapsePanel") {
1601
- panel.sizeBeforeCollapse = panel.currentValue.value.toNumber();
1602
- direction = direction.mul(new Big(-1));
1603
- }
1594
+ return new Promise<AnimationActorOutput | undefined>((resolve, reject) => {
1595
+ abortController.signal.addEventListener("abort", () => {
1596
+ reject(new Error("Operation was canceled"));
1597
+ });
1598
+
1599
+ if (event.type === "collapsePanel") {
1600
+ panel.sizeBeforeCollapse = panel.currentValue.value.toNumber();
1601
+ direction = direction.mul(new Big(-1));
1602
+ }
1604
1603
 
1605
- const fps = 60;
1606
- const { duration, ease } = getCollapseAnimation(panel);
1607
- const totalFrames = Math.ceil(
1608
- panel.collapseAnimation ? duration / (1000 / fps) : 1
1604
+ const fps = 60;
1605
+ const { duration, ease } = getCollapseAnimation(panel);
1606
+ const totalFrames = Math.ceil(
1607
+ panel.collapseAnimation ? duration / (1000 / fps) : 1
1608
+ );
1609
+ let frame = 0;
1610
+ let appliedDelta = new Big(0);
1611
+
1612
+ function renderFrame() {
1613
+ const progress = ++frame / totalFrames;
1614
+ const e = new Big(panel.collapseAnimation ? ease(progress) : 1);
1615
+ const delta = e.mul(fullDelta).sub(appliedDelta).mul(direction);
1616
+
1617
+ send({
1618
+ type: "applyDelta",
1619
+ handleId: handle.item.id,
1620
+ delta: delta.toNumber(),
1621
+ });
1622
+
1623
+ appliedDelta = appliedDelta.add(
1624
+ delta
1625
+ .abs()
1626
+ .mul(
1627
+ (delta.gt(0) && direction.lt(0)) || (delta.lt(0) && direction.gt(0))
1628
+ ? -1
1629
+ : 1
1630
+ )
1609
1631
  );
1610
- let frame = 0;
1611
- let appliedDelta = new Big(0);
1612
1632
 
1613
- function renderFrame() {
1614
- const progress = ++frame / totalFrames;
1615
- const e = new Big(panel.collapseAnimation ? ease(progress) : 1);
1616
- const delta = e.mul(fullDelta).sub(appliedDelta).mul(direction);
1633
+ if (e.eq(1)) {
1634
+ const action = event.type === "expandPanel" ? "expand" : "collapse";
1635
+ resolve({ panelId: panel.id, action });
1636
+ return false;
1637
+ }
1617
1638
 
1618
- send({
1619
- type: "applyDelta",
1620
- handleId: handle.item.id,
1621
- delta: delta.toNumber(),
1622
- });
1639
+ return true;
1640
+ }
1623
1641
 
1624
- appliedDelta = appliedDelta.add(
1625
- delta
1626
- .abs()
1627
- .mul(
1628
- (delta.gt(0) && direction.lt(0)) ||
1629
- (delta.lt(0) && direction.gt(0))
1630
- ? -1
1631
- : 1
1632
- )
1633
- );
1642
+ raf(renderFrame);
1643
+ });
1644
+ }
1634
1645
 
1635
- if (e.eq(1)) {
1636
- const action = event.type === "expandPanel" ? "expand" : "collapse";
1637
- resolve({ panelId: panel.id, action });
1638
- return false;
1639
- }
1646
+ function assign<T>(target: T, source: Partial<T>) {
1647
+ for (const key in source) {
1648
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1649
+ (target as any)[key] = source[key];
1650
+ }
1651
+ }
1652
+
1653
+ export interface GroupMachineInput {
1654
+ orientation?: Orientation;
1655
+ groupId: string;
1656
+ items?: Item[];
1657
+ autosaveStrategy?: "localStorage" | "cookie";
1658
+ }
1640
1659
 
1641
- return true;
1660
+ export type State = "idle" | "dragging" | "togglingCollapse";
1661
+
1662
+ let groupId = 0;
1663
+
1664
+ export function groupMachine(
1665
+ input: Partial<GroupMachineContextValue>,
1666
+ onUpdate?: (context: GroupMachineContextValue) => void
1667
+ ) {
1668
+ const abortController = new AbortController();
1669
+ const state = {
1670
+ current: "idle" as State,
1671
+ };
1672
+ let locked = false;
1673
+
1674
+ const context: GroupMachineContextValue = {
1675
+ size: { width: 0, height: 0 },
1676
+ items: input.items || [],
1677
+ orientation: input.orientation || "horizontal",
1678
+ dragOvershoot: new Big(0),
1679
+ groupId: input.groupId || `group-${groupId++}`,
1680
+ autosaveStrategy: input.autosaveStrategy,
1681
+ };
1682
+
1683
+ const actions = {
1684
+ prepare: () => {
1685
+ context.items = prepareItems(context);
1686
+ },
1687
+ clearLastKnownSize: () => {
1688
+ context.items = clearLastKnownSize(context.items);
1689
+ },
1690
+ commit: () => {
1691
+ context.dragOvershoot = new Big(0);
1692
+ context.items = commitLayout(context);
1693
+ },
1694
+ onResize: () => {
1695
+ for (const item of context.items) {
1696
+ if (isPanelData(item)) {
1697
+ const pixel = item.lastKnownSize
1698
+ ? new Big(
1699
+ context.orientation === "horizontal"
1700
+ ? item.lastKnownSize.width
1701
+ : item.lastKnownSize.height
1702
+ )
1703
+ : clampUnit(
1704
+ context,
1705
+ item,
1706
+ getUnitPixelValue(context, item.currentValue)
1707
+ );
1708
+ const groupSize = getGroupSize(context);
1709
+
1710
+ item.onResize?.current?.({
1711
+ pixel: pixel.toNumber(),
1712
+ percentage:
1713
+ groupSize > 0 ? pixel.div(getGroupSize(context)).toNumber() : -1,
1714
+ });
1715
+ }
1716
+ }
1717
+ },
1718
+ onAutosave: () => {
1719
+ if (!context.autosaveStrategy || typeof window === "undefined") {
1720
+ return;
1642
1721
  }
1643
1722
 
1644
- raf(renderFrame);
1645
- })
1646
- );
1647
-
1648
- export const groupMachine = createMachine(
1649
- {
1650
- initial: "idle",
1651
- types: {
1652
- context: {} as GroupMachineContextValue,
1653
- events: {} as GroupMachineEvent,
1654
- input: {} as {
1655
- orientation?: Orientation;
1656
- groupId: string;
1657
- initialItems?: Item[];
1658
- autosaveStrategy?: "localStorage" | "cookie";
1659
- },
1723
+ const snapshot = { ...context };
1724
+ snapshot.items = clearLastKnownSize(context.items);
1725
+ snapshot.activeDragHandleId = context.activeDragHandleId;
1726
+ const data = JSON.stringify(snapshot);
1727
+
1728
+ if (context.autosaveStrategy === "localStorage") {
1729
+ localStorage.setItem(context.groupId, data);
1730
+ } else {
1731
+ Cookies.set(context.groupId, data, {
1732
+ path: "/",
1733
+ "max-age": 31536000,
1734
+ });
1735
+ }
1660
1736
  },
1661
- context: ({ input }) => ({
1662
- size: { width: 0, height: 0 },
1663
- items: input.initialItems || [],
1664
- orientation: input.orientation || "horizontal",
1665
- dragOvershoot: new Big(0),
1666
- groupId: input.groupId,
1667
- autosaveStrategy: input.autosaveStrategy,
1668
- }),
1669
- states: {
1670
- idle: {
1671
- entry: ["onAutosave"],
1672
- on: {
1673
- dragHandleStart: {
1674
- target: "dragging",
1675
- actions: ["onDragHandleStart"],
1676
- },
1677
- setPanelPixelSize: {
1678
- actions: [
1679
- "prepare",
1680
- "onClearLastKnownSize",
1681
- "onSetPanelSize",
1682
- "commit",
1683
- "onResize",
1684
- "onAutosave",
1685
- ],
1686
- },
1687
- collapsePanel: [
1688
- {
1689
- actions: "notifyCollapseToggle",
1690
- guard: "shouldNotifyCollapseToggle",
1691
- },
1692
- { target: "togglingCollapse" },
1693
- ],
1694
- expandPanel: [
1695
- // This will match if we can't expand and the expansion won't happen
1696
- { guard: "cannotExpandPanel" },
1697
- {
1698
- actions: "notifyCollapseToggle",
1699
- guard: "shouldNotifyCollapseToggle",
1700
- },
1701
- { target: "togglingCollapse" },
1702
- ],
1703
- },
1704
- },
1705
- dragging: {
1706
- entry: ["prepare"],
1707
- on: {
1708
- dragHandle: {
1709
- actions: ["onClearLastKnownSize", "onDragHandle", "onResize"],
1710
- },
1711
- dragHandleEnd: { target: "idle" },
1712
- collapsePanel: {
1713
- guard: "shouldCollapseToggle",
1714
- actions: "runCollapseToggle",
1715
- },
1716
- expandPanel: {
1717
- guard: "shouldCollapseToggle",
1718
- actions: "runCollapseToggle",
1719
- },
1720
- },
1721
- exit: ["commit", "clearDragHandle"],
1722
- },
1723
- togglingCollapse: {
1724
- entry: ["prepare", "onClearLastKnownSize"],
1725
- invoke: {
1726
- src: "animation",
1727
- input: (i) => ({ ...i, send: i.self.send }),
1728
- onDone: {
1729
- target: "idle",
1730
- actions: ["onToggleCollapseComplete", "commit"],
1731
- },
1732
- },
1733
- on: {
1734
- applyDelta: { actions: ["onApplyDelta", "onResize"] },
1735
- },
1736
- },
1737
+ removeItem: (id: string) => {
1738
+ const itemIndex = context.items.findIndex((item) => item.id === id);
1739
+ const item = context.items[itemIndex];
1740
+
1741
+ if (!item) {
1742
+ return;
1743
+ }
1744
+
1745
+ const newItems = context.items.filter((i) => i.id !== id);
1746
+ const removedSize = isPanelData(item)
1747
+ ? item.currentValue.value
1748
+ : item.size.value;
1749
+
1750
+ applyDeltaInBothDirections(context, newItems, itemIndex, removedSize);
1751
+
1752
+ context.items = newItems;
1737
1753
  },
1738
- on: {
1739
- setActualItemsSize: { actions: ["recordActualItemSize", "onResize"] },
1740
- registerPanel: { actions: ["assignPanelData"] },
1741
- rebindPanelCallbacks: {
1742
- actions: ["rebindPanelCallbacks"],
1743
- },
1744
- updateConstraints: {
1745
- actions: [
1746
- "prepare",
1747
- "updateConstraints",
1748
- "onClearLastKnownSize",
1749
- "commit",
1750
- "onResize",
1751
- "onAutosave",
1752
- ],
1753
- },
1754
- registerDynamicPanel: {
1755
- actions: [
1756
- "prepare",
1757
- "onRegisterDynamicPanel",
1758
- "onClearLastKnownSize",
1759
- "commit",
1760
- "onResize",
1761
- "onAutosave",
1762
- ],
1763
- },
1764
- unregisterPanel: {
1765
- actions: [
1766
- "prepare",
1767
- "removeItem",
1768
- "onClearLastKnownSize",
1769
- "commit",
1770
- "onResize",
1771
- "onAutosave",
1772
- ],
1773
- },
1774
- registerPanelHandle: { actions: ["assignPanelHandleData"] },
1775
- unregisterPanelHandle: {
1776
- actions: [
1777
- "prepare",
1778
- "removeItem",
1779
- "onClearLastKnownSize",
1780
- "commit",
1781
- "onResize",
1782
- "onAutosave",
1783
- ],
1784
- },
1785
- setSize: { actions: ["updateSize", "onResize"] },
1786
- setOrientation: {
1787
- actions: ["updateOrientation", "onClearLastKnownSize", "onResize"],
1788
- },
1754
+ notifyCollapseToggle: (event: GroupMachineEvent) => {
1755
+ isEvent(event, ["collapsePanel", "expandPanel"]);
1756
+ const panel = getPanelWithId(context, event.panelId);
1757
+ panel.onCollapseChange?.current?.(!panel.collapsed);
1789
1758
  },
1790
- },
1791
- {
1792
- guards: {
1793
- shouldNotifyCollapseToggle: ({ context, event }) => {
1794
- isEvent(event, ["collapsePanel", "expandPanel"]);
1795
- const panel = getPanelWithId(context, event.panelId);
1796
- return !event.controlled && panel.collapseIsControlled === true;
1797
- },
1798
- shouldCollapseToggle: ({ context, event }) => {
1799
- isEvent(event, ["collapsePanel", "expandPanel"]);
1800
- const panel = getPanelWithId(context, event.panelId);
1801
- return panel.collapseIsControlled === true;
1802
- },
1803
- cannotExpandPanel: ({ context, event }) => {
1804
- isEvent(event, ["expandPanel"]);
1805
- const delta = getDeltaForEvent(context, event);
1806
- const handle = getHandleForPanelId(context, event.panelId);
1807
- const pixelItems = prepareItems(context);
1808
-
1809
- let interimContext = { ...context, items: pixelItems };
1810
- interimContext = {
1811
- ...interimContext,
1812
- ...iterativelyUpdateLayout({
1813
- context: interimContext,
1814
- handleId: handle.item.id,
1815
- controlled: event.controlled,
1816
- delta: delta,
1817
- direction: handle.direction,
1818
- }),
1819
- };
1820
- const updatedPanel = interimContext.items.find(
1821
- (i) => i.id === event.panelId
1822
- );
1823
- const totalSize = interimContext.items.reduce(
1824
- (acc, i) =>
1825
- acc.add(isPanelData(i) ? i.currentValue.value : i.size.value),
1826
- new Big(0)
1827
- );
1828
- const didExpand =
1829
- updatedPanel &&
1830
- isPanelData(updatedPanel) &&
1831
- !updatedPanel.currentValue.value.eq(updatedPanel.collapsedSize.value);
1759
+ runCollapseToggle: (event: GroupMachineEvent) => {
1760
+ isEvent(event, ["collapsePanel", "expandPanel"]);
1832
1761
 
1833
- return totalSize.gt(getGroupSize(context)) || !didExpand;
1834
- },
1762
+ const handle = getHandleForPanelId(context, event.panelId);
1763
+ // When collapsing a panel it will be in the opposite direction
1764
+ // that handle assumes
1765
+ const delta =
1766
+ event.type === "collapsePanel"
1767
+ ? handle.direction * -1
1768
+ : handle.direction;
1769
+ const newContext = updateLayout(context, {
1770
+ handleId: handle.item.id,
1771
+ type: "dragHandle",
1772
+ controlled: event.controlled,
1773
+ value: dragHandlePayload({ delta, orientation: context.orientation }),
1774
+ });
1775
+
1776
+ assign(context, newContext);
1835
1777
  },
1836
- actors: {
1837
- animation: animationActor,
1778
+ onAnimationEnd: (output: AnimationActorOutput | undefined) => {
1779
+ invariant(output, "Expected output from animation actor");
1780
+
1781
+ const panel = getPanelWithId(context, output.panelId);
1782
+ panel.collapsed = output.action === "collapse";
1783
+
1784
+ if (panel.collapsed) {
1785
+ panel.currentValue = panel.collapsedSize;
1786
+ }
1787
+
1788
+ actions.commit();
1838
1789
  },
1839
- actions: {
1840
- onDragHandleStart: assign({
1841
- activeDragHandleId: ({ event }) => {
1842
- isEvent(event, ["dragHandleStart"]);
1843
- return event.handleId;
1844
- },
1845
- }),
1846
- clearDragHandle: assign({
1847
- activeDragHandleId: undefined,
1848
- }),
1849
- onAutosave: ({ context, self }) => {
1850
- if (!context.autosaveStrategy || typeof window === "undefined") {
1851
- return;
1852
- }
1790
+ };
1853
1791
 
1854
- const snapshot = self.getPersistedSnapshot() as GroupMachineSnapshot;
1855
- snapshot.context.items = clearLastKnownSize(context.items);
1856
- snapshot.context.activeDragHandleId = context.activeDragHandleId;
1857
- snapshot.value = "idle";
1858
- const data = JSON.stringify(snapshot);
1792
+ actions.onAutosave();
1859
1793
 
1860
- if (context.autosaveStrategy === "localStorage") {
1861
- localStorage.setItem(context.groupId, data);
1862
- } else {
1863
- Cookies.set(context.groupId, data, {
1864
- path: "/",
1865
- "max-age": 31536000,
1866
- });
1867
- }
1868
- },
1869
- notifyCollapseToggle: ({ context, event }) => {
1870
- isEvent(event, ["collapsePanel", "expandPanel"]);
1871
- const panel = getPanelWithId(context, event.panelId);
1872
- panel.onCollapseChange?.current?.(!panel.collapsed);
1873
- },
1874
- runCollapseToggle: enqueueActions(({ context, event, enqueue }) => {
1875
- isEvent(event, ["collapsePanel", "expandPanel"]);
1876
-
1877
- const handle = getHandleForPanelId(context, event.panelId);
1878
- // When collapsing a panel it will be in the opposite direction
1879
- // that handle assumes
1880
- const delta =
1881
- event.type === "collapsePanel"
1882
- ? handle.direction * -1
1883
- : handle.direction;
1884
- const newContext = updateLayout(context, {
1794
+ const guards = {
1795
+ shouldNotifyCollapseToggle: (event: GroupMachineEvent) => {
1796
+ isEvent(event, ["collapsePanel", "expandPanel"]);
1797
+ const panel = getPanelWithId(context, event.panelId);
1798
+ return !event.controlled && panel.collapseIsControlled === true;
1799
+ },
1800
+ shouldCollapseToggle: (event: GroupMachineEvent) => {
1801
+ isEvent(event, ["collapsePanel", "expandPanel"]);
1802
+ const panel = getPanelWithId(context, event.panelId);
1803
+ return panel.collapseIsControlled === true;
1804
+ },
1805
+ cannotExpandPanel: (event: GroupMachineEvent) => {
1806
+ isEvent(event, ["expandPanel"]);
1807
+ const delta = getDeltaForEvent(context, event);
1808
+ const handle = getHandleForPanelId(context, event.panelId);
1809
+ const pixelItems = prepareItems(context);
1810
+
1811
+ let interimContext = { ...context, items: pixelItems };
1812
+ interimContext = {
1813
+ ...interimContext,
1814
+ ...iterativelyUpdateLayout({
1815
+ context: interimContext,
1885
1816
  handleId: handle.item.id,
1886
- type: "dragHandle",
1887
1817
  controlled: event.controlled,
1888
- value: dragHandlePayload({ delta, orientation: context.orientation }),
1889
- });
1818
+ delta: delta,
1819
+ direction: handle.direction,
1820
+ }),
1821
+ };
1822
+ const updatedPanel = interimContext.items.find(
1823
+ (i) => i.id === event.panelId
1824
+ );
1825
+ const totalSize = interimContext.items.reduce(
1826
+ (acc, i) =>
1827
+ acc.add(isPanelData(i) ? i.currentValue.value : i.size.value),
1828
+ new Big(0)
1829
+ );
1830
+ const didExpand =
1831
+ updatedPanel &&
1832
+ isPanelData(updatedPanel) &&
1833
+ !updatedPanel.currentValue.value.eq(updatedPanel.collapsedSize.value);
1890
1834
 
1891
- enqueue.assign(newContext);
1892
- }),
1893
- onToggleCollapseComplete: assign({
1894
- items: ({ context, event: e }) => {
1895
- const { output } = e as unknown as { output: AnimationActorOutput };
1896
- invariant(output, "Expected output from animation actor");
1835
+ return totalSize.gt(getGroupSize(context)) || !didExpand;
1836
+ },
1837
+ };
1897
1838
 
1898
- const panel = getPanelWithId(context, output.panelId);
1899
- panel.collapsed = output.action === "collapse";
1839
+ function transition(to: State) {
1840
+ // exit
1841
+ switch (state.current) {
1842
+ case "dragging":
1843
+ actions.commit();
1844
+ context.activeDragHandleId = undefined;
1845
+ break;
1846
+ }
1900
1847
 
1901
- if (panel.collapsed) {
1902
- panel.currentValue = panel.collapsedSize;
1903
- }
1848
+ // enter
1849
+ switch (to) {
1850
+ case "idle":
1851
+ actions.onAutosave();
1852
+ break;
1853
+ case "dragging":
1854
+ actions.prepare();
1855
+ break;
1856
+ case "togglingCollapse":
1857
+ actions.prepare();
1858
+ actions.clearLastKnownSize();
1859
+ break;
1860
+ }
1904
1861
 
1905
- return context.items;
1906
- },
1907
- }),
1908
- updateSize: assign({
1909
- size: ({ event }) => {
1910
- isEvent(event, ["setSize"]);
1911
- return event.size;
1912
- },
1913
- }),
1914
- recordActualItemSize: assign({
1915
- items: ({ context, event }) => {
1916
- isEvent(event, ["setActualItemsSize"]);
1917
-
1918
- const withLastKnownSize = context.items.map((i) => {
1919
- if (!isPanelData(i)) return i;
1920
- const lastKnownSize = event.childrenSizes[i.id] || i.lastKnownSize;
1921
- return { ...i, lastKnownSize };
1922
- });
1862
+ state.current = to;
1863
+ }
1923
1864
 
1924
- let totalSize = 0;
1865
+ function send(event: GroupMachineEvent) {
1866
+ switch (event.type) {
1867
+ case "lockGroup":
1868
+ locked = true;
1869
+ break;
1870
+ case "unlockGroup":
1871
+ locked = false;
1872
+ break;
1873
+ }
1925
1874
 
1926
- for (const item of withLastKnownSize) {
1927
- if (isPanelData(item)) {
1928
- const size =
1929
- item.lastKnownSize?.[
1930
- context.orientation === "horizontal" ? "width" : "height"
1931
- ];
1875
+ if (locked) {
1876
+ return;
1877
+ }
1932
1878
 
1933
- // If any size is 0 don't handle overflow
1934
- if (!size) {
1935
- return withLastKnownSize;
1936
- }
1879
+ switch (event.type) {
1880
+ case "registerPanel":
1881
+ context.items = addDeDuplicatedItems(context.items, {
1882
+ type: "panel",
1883
+ currentValue: makePixelUnit(-1),
1884
+ ...event.data,
1885
+ });
1886
+ break;
1887
+ case "unregisterPanel":
1888
+ actions.prepare();
1889
+ actions.removeItem(event.id);
1890
+ actions.clearLastKnownSize();
1891
+ actions.commit();
1892
+ actions.onResize();
1893
+ actions.onAutosave();
1894
+ break;
1895
+ case "registerPanelHandle": {
1896
+ const unit =
1897
+ typeof event.data.size === "string"
1898
+ ? parseUnit(event.data.size)
1899
+ : event.data.size;
1900
+
1901
+ context.items = addDeDuplicatedItems(context.items, {
1902
+ type: "handle",
1903
+ ...event.data,
1904
+ size: {
1905
+ type: "pixel",
1906
+ value: new Big(unit.value),
1907
+ },
1908
+ });
1909
+ break;
1910
+ }
1911
+ case "unregisterPanelHandle":
1912
+ actions.prepare();
1913
+ actions.removeItem(event.id);
1914
+ actions.clearLastKnownSize();
1915
+ actions.commit();
1916
+ actions.onResize();
1917
+ actions.onAutosave();
1918
+ break;
1919
+ case "registerDynamicPanel": {
1920
+ actions.prepare();
1937
1921
 
1938
- totalSize += size;
1939
- } else {
1940
- totalSize += item.size.value.toNumber();
1941
- }
1942
- }
1922
+ let currentValue: ParsedUnit = makePixelUnit(0);
1943
1923
 
1944
- if (totalSize > getGroupSize(context)) {
1945
- return handleOverflow({
1946
- ...context,
1947
- items: prepareItems({ ...context, items: withLastKnownSize }),
1948
- }).items;
1949
- }
1924
+ if (
1925
+ event.data.collapsible &&
1926
+ event.data.collapsed &&
1927
+ event.data.collapsedSize
1928
+ ) {
1929
+ currentValue = event.data.collapsedSize;
1930
+ } else if (event.data.default) {
1931
+ currentValue = event.data.default;
1932
+ } else {
1933
+ currentValue = event.data.min;
1934
+ }
1950
1935
 
1951
- return withLastKnownSize;
1952
- },
1953
- }),
1954
- updateOrientation: assign({
1955
- orientation: ({ event }) => {
1956
- isEvent(event, ["setOrientation"]);
1957
- return event.orientation;
1958
- },
1959
- }),
1960
- onClearLastKnownSize: assign({
1961
- items: ({ context }) => clearLastKnownSize(context.items),
1962
- }),
1963
- assignPanelData: assign({
1964
- items: ({ context, event }) => {
1965
- isEvent(event, ["registerPanel"]);
1966
-
1967
- return addDeDuplicatedItems(context.items, {
1968
- type: "panel",
1969
- currentValue: makePixelUnit(-1),
1970
- ...event.data,
1971
- });
1972
- },
1973
- }),
1974
- rebindPanelCallbacks: assign({
1975
- items: ({ context, event }) => {
1976
- isEvent(event, ["rebindPanelCallbacks"]);
1977
-
1978
- for (const item of context.items) {
1979
- if (isPanelData(item) && item.id === event.data.id) {
1980
- item.onCollapseChange = event.data.onCollapseChange;
1981
- item.onResize = event.data.onResize;
1936
+ const newItems = addDeDuplicatedItems(context.items, {
1937
+ type: "panel",
1938
+ ...event.data,
1939
+ currentValue,
1940
+ });
1941
+ const itemIndex = newItems.findIndex(
1942
+ (item) => item.id === event.data.id
1943
+ );
1944
+ const newContext = { ...context, items: newItems };
1945
+ const overflowDueToHandles = context.items
1946
+ .reduce((acc, i) => {
1947
+ if (isPanelHandle(i)) {
1948
+ return acc.add(getUnitPixelValue(context, i.size));
1982
1949
  }
1950
+
1951
+ return acc.add(i.currentValue.value);
1952
+ }, new Big(0))
1953
+ .minus(getGroupSize(context));
1954
+
1955
+ applyDeltaInBothDirections(
1956
+ newContext,
1957
+ newItems,
1958
+ itemIndex,
1959
+ currentValue.value.add(overflowDueToHandles).neg()
1960
+ );
1961
+
1962
+ context.items = newItems;
1963
+
1964
+ actions.clearLastKnownSize();
1965
+ actions.commit();
1966
+ actions.onResize();
1967
+ actions.onAutosave();
1968
+ break;
1969
+ }
1970
+ case "rebindPanelCallbacks":
1971
+ for (const item of context.items) {
1972
+ if (isPanelData(item) && item.id === event.data.id) {
1973
+ item.onCollapseChange = event.data.onCollapseChange;
1974
+ item.onResize = event.data.onResize;
1983
1975
  }
1976
+ }
1977
+ break;
1978
+ case "updateConstraints":
1979
+ actions.prepare();
1984
1980
 
1985
- return context.items;
1986
- },
1987
- }),
1988
- updateConstraints: assign({
1989
- items: ({ context, event }) => {
1990
- isEvent(event, ["updateConstraints"]);
1991
-
1992
- for (const item of context.items) {
1993
- if (isPanelData(item) && item.id === event.data.id) {
1994
- const panel = event.data as PanelData;
1995
- item.min = panel.min;
1996
- item.max = panel.max;
1997
- item.default = panel.default;
1998
- item.collapsedSize = panel.collapsedSize;
1999
- item.isStaticAtRest = panel.isStaticAtRest;
2000
- item.collapseAnimation = panel.collapseAnimation;
2001
- item.collapsible = panel.collapsible;
2002
- } else if (isPanelHandle(item) && item.id === event.data.id) {
2003
- const handle = event.data as PanelHandleData;
2004
- item.size = handle.size;
2005
- }
1981
+ for (const item of context.items) {
1982
+ if (isPanelData(item) && item.id === event.data.id) {
1983
+ const panel = event.data as PanelData;
1984
+ item.min = panel.min;
1985
+ item.max = panel.max;
1986
+ item.default = panel.default;
1987
+ item.collapsedSize = panel.collapsedSize;
1988
+ item.isStaticAtRest = panel.isStaticAtRest;
1989
+ item.collapseAnimation = panel.collapseAnimation;
1990
+ item.collapsible = panel.collapsible;
1991
+ } else if (isPanelHandle(item) && item.id === event.data.id) {
1992
+ const handle = event.data as PanelHandleData;
1993
+ item.size = handle.size;
2006
1994
  }
1995
+ }
2007
1996
 
2008
- return context.items;
2009
- },
2010
- }),
2011
- onRegisterDynamicPanel: assign({
2012
- items: ({ context, event }) => {
2013
- isEvent(event, ["registerDynamicPanel"]);
1997
+ actions.clearLastKnownSize();
1998
+ actions.commit();
1999
+ actions.onResize();
2000
+ actions.onAutosave();
2001
+ break;
2002
+ case "setActualItemsSize": {
2003
+ const withLastKnownSize = context.items.map((i) => {
2004
+ if (!isPanelData(i)) return i;
2005
+ const lastKnownSize = event.childrenSizes[i.id] || i.lastKnownSize;
2006
+ return { ...i, lastKnownSize };
2007
+ });
2014
2008
 
2015
- let currentValue: ParsedUnit = makePixelUnit(0);
2009
+ let totalSize = 0;
2010
+ let useLastKnownSize = false;
2016
2011
 
2017
- if (
2018
- event.data.collapsible &&
2019
- event.data.collapsed &&
2020
- event.data.collapsedSize
2021
- ) {
2022
- currentValue = event.data.collapsedSize;
2023
- } else if (event.data.default) {
2024
- currentValue = event.data.default;
2012
+ for (const item of withLastKnownSize) {
2013
+ if (isPanelData(item)) {
2014
+ const size =
2015
+ item.lastKnownSize?.[
2016
+ context.orientation === "horizontal" ? "width" : "height"
2017
+ ];
2018
+
2019
+ // If any size is 0 don't handle overflow
2020
+ if (!size) {
2021
+ context.items = withLastKnownSize;
2022
+ useLastKnownSize = true;
2023
+ break;
2024
+ }
2025
+
2026
+ totalSize += size;
2025
2027
  } else {
2026
- currentValue = event.data.min;
2028
+ totalSize += item.size.value.toNumber();
2027
2029
  }
2030
+ }
2028
2031
 
2029
- const newItems = addDeDuplicatedItems(context.items, {
2030
- type: "panel",
2031
- ...event.data,
2032
- currentValue,
2033
- });
2034
- const itemIndex = newItems.findIndex(
2035
- (item) => item.id === event.data.id
2036
- );
2037
- const newContext = { ...context, items: newItems };
2038
- const overflowDueToHandles = context.items
2039
- .reduce((acc, i) => {
2040
- if (isPanelHandle(i)) {
2041
- return acc.add(getUnitPixelValue(context, i.size));
2042
- }
2032
+ if (useLastKnownSize) {
2033
+ context.items = withLastKnownSize;
2034
+ } else if (totalSize > getGroupSize(context)) {
2035
+ context.items = handleOverflow({
2036
+ ...context,
2037
+ items: prepareItems({ ...context, items: withLastKnownSize }),
2038
+ }).items;
2039
+ } else {
2040
+ context.items = withLastKnownSize;
2041
+ }
2043
2042
 
2044
- return acc.add(i.currentValue.value);
2045
- }, new Big(0))
2046
- .minus(getGroupSize(context));
2043
+ actions.onResize();
2044
+ break;
2045
+ }
2046
+ case "setSize": {
2047
+ context.size = event.size;
2048
+ actions.onResize();
2049
+ break;
2050
+ }
2051
+ case "setOrientation":
2052
+ context.orientation = event.orientation;
2053
+ actions.clearLastKnownSize();
2054
+ actions.onResize();
2055
+ break;
2056
+ default:
2057
+ break;
2058
+ }
2047
2059
 
2048
- applyDeltaInBothDirections(
2049
- newContext,
2050
- newItems,
2051
- itemIndex,
2052
- currentValue.value.add(overflowDueToHandles).neg()
2060
+ if (state.current === "idle") {
2061
+ switch (event.type) {
2062
+ case "dragHandleStart":
2063
+ transition("dragging");
2064
+ context.activeDragHandleId = event.handleId;
2065
+ break;
2066
+ case "setPanelPixelSize": {
2067
+ actions.prepare();
2068
+ actions.clearLastKnownSize();
2069
+
2070
+ const panel = getPanelWithId(context, event.panelId);
2071
+ const handle = getHandleForPanelId(context, event.panelId);
2072
+ const current = panel.currentValue.value;
2073
+ const newSize = clampUnit(
2074
+ context,
2075
+ panel,
2076
+ getUnitPixelValue(context, parseUnit(event.size))
2053
2077
  );
2078
+ const isBigger = newSize > current;
2079
+ const delta = isBigger
2080
+ ? newSize.minus(current)
2081
+ : current.minus(newSize);
2054
2082
 
2055
- return newItems;
2056
- },
2057
- }),
2058
- assignPanelHandleData: assign({
2059
- items: ({ context, event }) => {
2060
- isEvent(event, ["registerPanelHandle"]);
2061
-
2062
- const unit =
2063
- typeof event.data.size === "string"
2064
- ? parseUnit(event.data.size)
2065
- : event.data.size;
2066
-
2067
- return addDeDuplicatedItems(context.items, {
2068
- type: "handle",
2069
- ...event.data,
2070
- size: {
2071
- type: "pixel",
2072
- value: new Big(unit.value),
2073
- },
2074
- });
2075
- },
2076
- }),
2077
- removeItem: assign({
2078
- items: ({ context, event }) => {
2079
- isEvent(event, ["unregisterPanel", "unregisterPanelHandle"]);
2080
- const itemIndex = context.items.findIndex(
2081
- (item) => item.id === event.id
2083
+ Object.assign(
2084
+ context,
2085
+ iterativelyUpdateLayout({
2086
+ context,
2087
+ direction: (handle.direction * (isBigger ? 1 : -1)) as -1 | 1,
2088
+ handleId: handle.item.id,
2089
+ delta,
2090
+ })
2082
2091
  );
2083
- const item = context.items[itemIndex];
2084
2092
 
2085
- if (!item) {
2086
- return context.items;
2093
+ actions.commit();
2094
+ actions.onResize();
2095
+ actions.onAutosave();
2096
+ break;
2097
+ }
2098
+ case "collapsePanel":
2099
+ if (guards.shouldNotifyCollapseToggle(event)) {
2100
+ actions.notifyCollapseToggle(event);
2101
+ } else {
2102
+ transition("togglingCollapse");
2103
+ abortController.abort();
2104
+ animationActor(context, event, send, abortController).then(
2105
+ (output) => {
2106
+ actions.onAnimationEnd(output);
2107
+ transition("idle");
2108
+ }
2109
+ );
2110
+ }
2111
+ break;
2112
+ case "expandPanel":
2113
+ if (guards.cannotExpandPanel(event)) {
2114
+ break;
2115
+ } else {
2116
+ if (guards.shouldNotifyCollapseToggle(event)) {
2117
+ actions.notifyCollapseToggle(event);
2118
+ } else {
2119
+ transition("togglingCollapse");
2120
+ abortController.abort();
2121
+ animationActor(context, event, send, abortController).then(
2122
+ (output) => {
2123
+ actions.onAnimationEnd(output);
2124
+ transition("idle");
2125
+ }
2126
+ );
2127
+ }
2087
2128
  }
2088
2129
 
2089
- const newItems = context.items.filter((i) => i.id !== event.id);
2090
- const removedSize = isPanelData(item)
2091
- ? item.currentValue.value
2092
- : item.size.value;
2130
+ break;
2131
+ default:
2132
+ break;
2133
+ }
2134
+ } else if (state.current === "dragging") {
2135
+ switch (event.type) {
2136
+ case "dragHandle":
2137
+ actions.clearLastKnownSize();
2138
+ assign(context, updateLayout(context, event));
2139
+ actions.onResize();
2140
+ break;
2141
+ case "dragHandleEnd":
2142
+ transition("idle");
2143
+ break;
2144
+ case "expandPanel":
2145
+ case "collapsePanel":
2146
+ if (guards.shouldCollapseToggle(event)) {
2147
+ actions.runCollapseToggle(event);
2148
+ }
2149
+ break;
2150
+ }
2151
+ } else if (state.current === "togglingCollapse") {
2152
+ switch (event.type) {
2153
+ case "applyDelta":
2154
+ assign(
2155
+ context,
2156
+ updateLayout(context, {
2157
+ handleId: event.handleId,
2158
+ type: "collapsePanel",
2159
+ disregardCollapseBuffer: true,
2160
+ value: dragHandlePayload({
2161
+ delta: event.delta,
2162
+ orientation: context.orientation,
2163
+ }),
2164
+ })
2165
+ );
2166
+ actions.onResize();
2167
+ break;
2168
+ }
2169
+ }
2093
2170
 
2094
- applyDeltaInBothDirections(context, newItems, itemIndex, removedSize);
2171
+ onUpdate?.(context);
2172
+ }
2095
2173
 
2096
- return newItems;
2097
- },
2098
- }),
2099
- prepare: assign({
2100
- items: ({ context }) => prepareItems(context),
2101
- }),
2102
- onDragHandle: enqueueActions(({ context, event, enqueue }) => {
2103
- isEvent(event, ["dragHandle"]);
2104
- enqueue.assign(updateLayout(context, event));
2105
- }),
2106
- commit: assign({
2107
- dragOvershoot: new Big(0),
2108
- items: ({ context }) => commitLayout(context),
2109
- }),
2110
- onApplyDelta: assign(({ context, event }) => {
2111
- isEvent(event, ["applyDelta"]);
2112
- return updateLayout(context, {
2113
- handleId: event.handleId,
2114
- type: "collapsePanel",
2115
- disregardCollapseBuffer: true,
2116
- value: dragHandlePayload({
2117
- delta: event.delta,
2118
- orientation: context.orientation,
2119
- }),
2120
- });
2121
- }),
2122
- onSetPanelSize: enqueueActions(({ context, event, enqueue }) => {
2123
- isEvent(event, ["setPanelPixelSize"]);
2124
-
2125
- const panel = getPanelWithId(context, event.panelId);
2126
- const handle = getHandleForPanelId(context, event.panelId);
2127
- const current = panel.currentValue.value;
2128
- const newSize = clampUnit(
2129
- context,
2130
- panel,
2131
- getUnitPixelValue(context, parseUnit(event.size))
2132
- );
2133
- const isBigger = newSize > current;
2134
- const delta = isBigger
2135
- ? newSize.minus(current)
2136
- : current.minus(newSize);
2174
+ return [context, send, state] as const;
2175
+ }
2137
2176
 
2138
- enqueue.assign(
2139
- iterativelyUpdateLayout({
2140
- context,
2141
- direction: (handle.direction * (isBigger ? 1 : -1)) as -1 | 1,
2142
- handleId: handle.item.id,
2143
- delta,
2144
- })
2145
- );
2146
- }),
2147
- onResize: ({ context }) => {
2148
- for (const item of context.items) {
2149
- if (isPanelData(item)) {
2150
- const pixel = item.lastKnownSize
2151
- ? new Big(
2152
- context.orientation === "horizontal"
2153
- ? item.lastKnownSize.width
2154
- : item.lastKnownSize.height
2155
- )
2156
- : clampUnit(
2157
- context,
2158
- item,
2159
- getUnitPixelValue(context, item.currentValue)
2160
- );
2161
- const groupSize = getGroupSize(context);
2162
-
2163
- item.onResize?.current?.({
2164
- pixel: pixel.toNumber(),
2165
- percentage:
2166
- groupSize > 0
2167
- ? pixel.div(getGroupSize(context)).toNumber()
2168
- : -1,
2169
- });
2170
- }
2171
- }
2172
- },
2173
- },
2174
- }
2175
- );
2177
+ export type SendFn = ReturnType<typeof groupMachine>[1];
2176
2178
 
2177
2179
  // #endregion